Rust 入門 (Rust Rocks)

  • 緣起
  • 實踐出真知
    • 快速獲取
    • 澄清概念
      • Ownership
      • Move
      • Reference
      • Mutable reference
    • 解釋錯誤
    • 數(shù)據(jù)競態(tài)條件
    • 構(gòu)建樹狀結(jié)構(gòu)
    • 渲染樹狀結(jié)構(gòu)
  • 總結(jié)
  • 源碼 Github

TL;DR

下面我對內(nèi)部分享話題的引言部分

Rust 是一門系統(tǒng)編程語言统抬,也是許多區(qū)塊鏈底層編程語言胎食,不論是舊歡 Parity撑碴,還是新貴 Libra蝙场;不論是微軟還是 Linux 的核心開發(fā)者都對它青眼有加。

Rust 有一些很迷人的地方否过,比如:AOT午笛,內(nèi)存安全性惭蟋,空指針安全性苗桂,還是豐富的類型系統(tǒng),異或是龐大的社區(qū)生態(tài)告组。這些別的語言也有煤伟,但是都沒有這么徹底。

Rust 的特點也十分突出木缝,Ownership 和 borrow便锨、references 還有 shadow,令人眼花繚亂我碟,最最有趣的就是你寫一段程序放案,編譯器會 blame 你很多,你就感覺是在和編譯器搏斗矫俺。

學習 Rust 不只是一時興起(早在2015年我就聽聞過)吱殉,也是一種擁抱變化的態(tài)度掸冤,重要的是可以讓你在看眾多區(qū)塊鏈代碼時不會那么心慌。

語言的趨勢反映了未來的主流開發(fā)群體的預測友雳,這點 Rust 確實算是后起之秀稿湿。

本次話題,我會講解我學習 Rust 的過程押赊,希望指導大家在學習新編程語言時應該怎么做才更高效饺藤,同時會用一段小程序 tree 來給大家演示它的與眾不同之處。

緣起

做區(qū)塊鏈的基本幾乎沒有人不知道 Rust 這門編程語言流礁,它非常受區(qū)塊鏈底層開發(fā)人員的青睞涕俗。說來也奇怪,Rust 起源于 Mozilla神帅,唯一大規(guī)模應用就是 Firefox咽袜,作為小眾語言卻在區(qū)塊鏈圈子里火了。這其中應該和以太坊的發(fā)起人 Govin Wood 創(chuàng)建的 Parity 項目有關枕稀,Parity 是一款用 Rust 編寫的以太坊客戶端询刹。

最初接觸 Rust 的時間大概是 2015 年,當年有同事發(fā)了一封“是否對 Rust 編程語言感興趣的”的郵件萎坷。當時年少不懂事熱血凹联,覺得這門語言因為它小眾很酷,所以特別適合拿來練功哆档,所以就激情地回應了郵件蔽挠,結(jié)果之后就沒有了下文,想必那位同事也因為響應的人數(shù)太少而興致缺缺瓜浸。

第二次關注 Rust 是因為陳天在自己的公眾號中提到了這門語言澳淑。我比較欣賞陳天,當初學習 Elixir 也是受他影響插佛,所以也跟著他的步伐去聽了張漢東的知乎Live杠巡,然后加入了他的讀者群(魅力Rust),在這個群中潛水了大半年雇寇,一直很驚嘆這個群的活躍度氢拥。

2019年,區(qū)塊鏈圈中的一次大事件是 Facebook 要發(fā)非主權(quán)貨幣 Libra锨侯,隨之而來是基于 Rust 之上的 Move 編程語言嫩海。這個 Move 說白了就是 Rust 的一種 DSL,用比較學術的話說是指稱(denotational)語義囚痴,用簡單的編譯器把 Move 的語法翻譯成 Rust 的語法然后借助 Rust 的編譯器生成二進制碼叁怪。這個過程沒有什么驚喜,不過 Move 語言顯然是借鑒了 Rust 中移交(Move)主權(quán)(Ownership)的概念深滚,它表征了這樣一種事實——數(shù)字資產(chǎn)只能有一個主人奕谭,一旦移動耳璧,就會發(fā)生主權(quán)轉(zhuǎn)移,以前的主人就喪失了該主權(quán)展箱。這種想法和 Rust 中主權(quán)管理非常契合旨枯,所以不難理解為什么 Libra 的開發(fā)團隊把名字也照搬過來了。當然混驰,Libra 的底層區(qū)塊鏈也用的是 Rust攀隔。這個大事件加上以太坊 Parity 的珠玉在前,對于程序員這群天生喜歡新鮮事物的人類而言栖榨,學習 Rust 的熱情必然水漲船高昆汹。

大概就是在這種契機下,我開始學習 Rust 的婴栽。依照老規(guī)矩满粗,我還是會從 tree 這個命令行程序入手,在試錯中逐步學習 Rust 這門語言愚争。包含它的基本數(shù)據(jù)類型映皆,組合數(shù)據(jù)類型,控制流轰枝,模塊(函數(shù))以及文件和集合操作捅彻,還有最關鍵的 Ownership 的應用。

實踐出真知

學習 Rust 最深刻的體驗莫過于和編譯器較勁鞍陨,這也是我聽到過最多的抱怨步淹。我想許多新手看到這么多警告或者錯誤,嘴上不說诚撵,心里應該很不是滋味缭裆。但是這也是 Rust 引以為豪的設計哲學。

每一門新進的語言都有自己的本質(zhì)原因(Rationale)或者設計哲學寿烟,比如 Lisp 家族的 Clojure 就有 Elegance and familiarity are orthogonal 的玄言妙語澈驼;往遠古追溯,Java 的 Write Once, Run Anywhere 豪言壯語韧衣;而 Rust 的基本設計哲學是 If it compiles, then it works盅藻,這個條件有多苛刻我們稍微想一想就能知道——動態(tài)弱類型語言向靜態(tài)強類型語言的逐步趨同態(tài)勢,基本已經(jīng)宣告了類型系統(tǒng)的勝利畅铭。

但即便如此,現(xiàn)代軟件工程也還是處處強調(diào)程序員要手寫各種測試確保代碼運行時的正確性——從單元測試到集成測試勃蜘,從冒煙測試到回歸測試硕噩,從 Profiling 到性能測試。這些測試方法和工具已經(jīng)深入到軟件工程的方方面面缭贡,然而各類軟件還是漏洞百出炉擅。Rust 發(fā)出這種高調(diào)宣言辉懒,不免有夜郎自大之嫌疑。不過程序?qū)檬莻€能造概念也能落地概念的神奇圈子谍失,高調(diào)的牛吹著吹著也就實現(xiàn)了眶俩。況且,Rust 充分詮釋了現(xiàn)代編程語言的核心思想——約束程序員快鱼,不是勸勸你的意思颠印,是憋死你的意思。

我在《我是如何學習新的編程語言》中說過學習的最好方式是有目的地試錯抹竹,我時常拿來練手的程序叫tree - list contents of directories in a tree-like format. 這段程序需要用到的 Rust 基本構(gòu)件有:

基礎概念
1. 變量 - let
2. ownership borrow - &
3. 可變性 - mut
4. 可變引用 - &mut


復合數(shù)據(jù)類型
1. String - String::from("") // 非基本類型
2. Slice - "" or vec[..]
2. struct - struct {}

集合及其操作
1. Vec<_> - Vec::new() // 考慮到集合需要自動擴展
2. iter()
3. .map()
4. .enumerate()
5. .flatten()
6. .collect()
7. .extend() //集合拼接

控制語句
1. if Expressions - if {} else {}
2. recursions

模塊
1. fn - fn x(s: String) -> Vec<String>

功能組件
1. Path
2. fs
3. env

當嘗試尋找這些元素時线罕,我發(fā)現(xiàn) Rust 或者諸如此類的編譯型語言都有一個讓人不舒服的地方——驗證的前置步驟耗時太長。因為沒有repl窃判,所以想去了解一些概念的使用方法钞楼,就不得不另外創(chuàng)建一個項目(我可不想污染當前項目的代碼),在它的 main 函數(shù)里編寫試驗程序袄琳,這比起具有快速反饋能力的repl询件,著實太慢了。不過這里的慢也是相對的唆樊,Rust 也有一個顯著的優(yōu)勢雳殊,在出現(xiàn)編譯錯誤時,編譯器不僅能向你解釋原因窗轩,還能推薦潛在的修改方式夯秃,這就比 Javascript 一類的動態(tài)語言要清晰和高明得多。再利用內(nèi)置的 assert_eq! 等斷言函數(shù)預判結(jié)果痢艺,又比單獨寫測試省事仓洼。所以,總體而言堤舒,學習的過程還是很愉悅的色建。

快速獲取

這里舉個例子,為了解如何拼接兩個集合時舌缤,需要事先搞明白幾個問題:

  1. 集合的構(gòu)造箕戳?
  2. 集合的拼接?
  3. 結(jié)果的斷言国撵?

在沒有repl的條件下陵吸,唯一快速上手的工具就是文檔,在 https://doc.rust-lang.org/std/ 的官方標準庫中介牙,可以搜到Struct std::vec::Vec詳細解釋壮虫。

通過例子程序,可以很快知道集合的構(gòu)造方式如下:

let mut v = vec![1, 2, 3];
v.reverse();
assert_eq!(v, [3, 2, 1]);

vec! 宏可以快速構(gòu)造出一個集合來,順便試驗下它的reverse方法囚似。那么集合如何拼接呢剩拢?為了解答這個問題,我一般會用搜索引擎饶唤,或者深入文檔徐伐,查找如 concatappend等關鍵字募狂,每每總有收獲办素。

在不考慮非功能需求的前提下,我們先用最直接的方式實現(xiàn)熬尺,例如:文檔中給出的樣例extend方法

let v = vec![1, 2, 3];
v.extend([1, 2, 3].iter().cloned()); // 編譯錯誤

注意摸屠,這里編譯失敗。Rust 編譯器會直截了當?shù)亟o出錯誤信息粱哼。

error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable
  --> src/main.rs:13:5
   |
12 |     let v = vec![1, 2, 3];
   |         - help: consider changing this to be mutable: `mut v`
13 |     v.extend([1, 2, 3].iter().cloned());
   |     ^ cannot borrow as mutable

錯誤信息中透露出我們的程序在嘗試借用(borrow)一個不可變的變量季二。borrowmutable都是新的概念。對于新的概念揭措,我們會習慣地用熟知的知識去類比胯舷。如果套用函數(shù)式編程中不可變的特性,大體可以猜到 Rust 中的變量默認是不可變的绊含。但是 cannot borrow as mutableborrow 確實是有點超出認知范圍桑嘶。那么此時弄清定義是非常有必要的。

澄清概念

學習語言的過程中最需要注意的事項就是澄清概念躬充。當遇到嶄新的概念時逃顶,我們得停下先去補充這部分的知識,然后再回過頭來理解和解決實際遇到的問題充甚。因為每一門編程語言都有本門派的哲學原理以政,它本身就萃取了多種理論和實踐的成果,所以必須學習這些概念伴找。學習的過程其實就是逐步澄清概念的過程盈蛮。

在學習(嘗試定義)borrow 的過程中,我又先后接觸到了 ownership, move, reference, mutable reference 等概念技矮。所以我定義了這些概念:

Ownership

變量擁有它指稱的值的所有權(quán)抖誉。
在 Rust 當中,變量擁有它指稱的值衰倦,即變量(variable)是它指稱值(value)的主人(owner)袒炉,值一次只能有一個主人,一旦主人離開作用域它的值就會被銷毀耿币。

Move

把一個變量的值重新賦值給另一個變量的行為梳杏。
根據(jù) Ownership 的定義,值一次只能有一個主人淹接,所以此時該值的所有權(quán)會被轉(zhuǎn)移給另一個變量十性,原來的變量就喪失了對這個值的所有權(quán),導致的直接影響就是這個變量此后不再可用塑悼。

Reference

一個變量指向(refer to)值而非擁有該值的所有權(quán)的狀態(tài)劲适。
在很多賦值的場景,包括變量賦值或者函數(shù)參數(shù)賦值厢蒜,我們并不希望之后原來的變量不再可用霞势,此時可以通過&(ampersands創(chuàng)建一個指向值的引用,將引用進行賦值時不會發(fā)生 Move斑鸦,所以原來的變量依舊可用愕贡。這種賦值行為被稱為borrow(借用)。結(jié)合實際巷屿,我們擁有的物品可以出借給別人固以,別人享有該物品的使用權(quán)(Possession),而非所有權(quán)(Ownership)嘱巾。

Mutable reference

標識該引用的值是可變的憨琳。

很多場景下,我們希望引用傳遞的值是可以改變的旬昭。此時我們就必須通過&mut標識該引用篙螟,否則不允許修改操作發(fā)生。值得注意的是问拘,&mut標識要求原來的變量也必須是mut的遍略,這很好理解,可變的變量的引用也得可變骤坐。而且為了防止數(shù)據(jù)競態(tài)條件的發(fā)生绪杏,在同一個作用域下,&mut的引用只能有一個或油,因為一旦出現(xiàn)多個可變引用寞忿,就可能遭遇不可重復讀風險(注意,Rust 保證這里沒有并行修改的風險)顶岸。而且同一個值的&mut&的引用不能共存腔彰,因為我們不希望一個只讀&的值同時還能被寫&mut,這樣會導致歧義辖佣。

解釋錯誤

澄清了必要概念以后霹抛,我們再來回顧上面的代碼。先去看一下這個extend函數(shù)的定義:

fn extend<I>(&mut self, iter: I)
where
    I: IntoIterator<Item = T>, 
Extends a collection with the contents of an iterator...

原來v.extend只是一個語法糖卷谈,真正的方法調(diào)用會把self作為第一個參數(shù)傳遞到extend(&mut self, iter: I)當中杯拐。可變引用作為函數(shù)參數(shù)賦值,那么自然原來的變量也必須聲明成可變的端逼。

所以我們照著它的指示修正如下:

let mut v = vec![1, 2, 3]; // 加上一個mut修飾符
v.extend([1, 2, 3].iter().cloned());

這回編譯器消停了朗兵,利用assert_eq!,我們來驗證extend操作的正確性顶滩。

assert_eq!(v, [1, 2, 3, 1, 2, 3]);

另外余掖,值得注意的是,Rust 和我們熟悉的函數(shù)式編程有些不同礁鲁,集合的拼接不會產(chǎn)生一個新的集合盐欺,而是對原有的集合進行修改。一般情況下仅醇,我們都會警惕可能會出現(xiàn)數(shù)據(jù)的競態(tài)條件——多個線程對該集合進行寫入操作怎么辦冗美?帶著這個問題,我們反思一下什么是數(shù)據(jù)的競態(tài)條件析二。

數(shù)據(jù)競態(tài)條件

數(shù)據(jù)競態(tài)條件發(fā)生的必要條件有:

  1. 多個引用同時指向相同的數(shù)據(jù)粉洼;
  2. 至少有一個引用在寫數(shù)據(jù);
  3. 對于數(shù)據(jù)的訪問沒有同步機制甲抖。

考察1和2:
假如此處有兩個引用指向同一個集合漆改,如下:

let mut v = vec![1, 2, 3];
let r1 = &mut v;
let r2 = &mut v;
assert_eq!(r1, r2);

編譯器會立即給出編譯錯誤

error[E0499]: cannot borrow `v` as mutable more than once at a time
--> src/main.rs:13:10
|
12 | let r1 = &mut v;
|          ------ first mutable borrow occurs here
13 | let r2 = &mut v;
|          ^^^^^^ second mutable borrow occurs here
14 | assert_eq!(r1, r2);
| ------------------- first borrow later used here

也就是說,在指定的作用域下只能有一個可變引用准谚。為什么要如此設計呢挫剑?在單線程下,這好像并不會出現(xiàn)數(shù)據(jù)競爭的問題[1]柱衔。不過考慮到下面這種場景的語義樊破,我們思考一下。

let mut v = vec![1, 2, 3];
let r1 = &mut v;
let r2 = &mut v;
assert_eq!(r2[1], 2);
*r1 = vec![0]
assert_eq!(r2[1], 2); // 失效

一旦允許r1改變數(shù)據(jù)唆铐,那對于r2而言哲戚,它先前持有的數(shù)據(jù)就已經(jīng)發(fā)生改變甚至失效,再拿來使用就有問題了艾岂,在上面這個例子當中顺少,*r1解除引用后被重新賦值,導致v的值隨之改變王浴,但是r2并不知情脆炎,依舊使用r2[1]導致此處越界。這個問題和數(shù)據(jù)庫中事務的不可重復讀(提交讀)的隔離級別類似氓辣,但是在單線程下這并不能算作充分的理由秒裕,只是說在語義層面有細微的不自然,留待后續(xù)研究钞啸。

蹊蹺的是几蜻,如果我將兩個可變引用放到不同的函數(shù)中喇潘,同樣的邏輯卻可以繞過編譯器錯誤。

fn main() {
    let mut v = vec![1, 2, 3];
    mut1(&mut v);
    mut2(&mut v);
}

fn mut1(v: &mut Vec<i32>) {
    *v = vec![0];
}

fn mut2(v: &mut Vec<i32>) {
    println!("{}", v[1]); // panicked at 'index out of bounds' 運行時錯誤
}

可見梭稚,上述的論述并沒有解釋清楚在單線程下同一個作用域下限制多個可變引用的根本原因颖低。

對于&mut&其實也可以做同樣的解釋捎琐。所以&mut&在 Rust 同一個作用域中無法共存这难。

考察3:
至于在多線程的環(huán)境下,是否會出現(xiàn)數(shù)據(jù)競態(tài)條件,我們得看 Rust 在線程使用方面的限制扼褪。在 Rust 的上下文里,使用Thread::spawn的線程時必須 Move 所有權(quán)[2]粱栖,因為在 Rust 看來话浇,Thread 的 LifeTime(生命周期)會比調(diào)用它的函數(shù)的生命周期的長,如果不 Move 所有權(quán)闹究,那么線程中數(shù)據(jù)就會在調(diào)用函數(shù)結(jié)束后釋放掉變量的內(nèi)存幔崖,導致線程中的數(shù)據(jù)無效。所以渣淤,這樣的限制是很有必要的赏寇,但反過來想,一旦數(shù)據(jù)的所有權(quán)發(fā)生轉(zhuǎn)移价认,那么多個線程并行修改同樣數(shù)據(jù)的可能性也就不復存在嗅定。

構(gòu)建樹狀結(jié)構(gòu)

struct Entry {
    name: String,
    children: Vec<Entry> 
}

fn tree(path: &Path) -> Entry {
    Entry{
        name: path.file_name()
            .and_then(|name| name.to_str())
            .map_or(String::from("."), |str| String::from(str)),
       
        children: if path.is_dir() {
            children(path)
        } else {
            Vec::new()
        }
    }
}

既然是樹狀結(jié)構(gòu),定義的結(jié)構(gòu)體就是遞歸的用踩。這里的struct Entry {}就是一種遞歸的結(jié)構(gòu)渠退。我想實現(xiàn)的樹狀結(jié)構(gòu)大致如下:

entry :: {name, [child]}
child :: entry

Rust 中沒有顯式的return,最后一個表達式的結(jié)果會被當成返回值脐彩,所以此處整個Entry結(jié)構(gòu)體會被返回碎乃。

path.file_name()
 .and_then(|name| name.to_str())
 .map_or(String::from("."), |str| String::from(str)),

這段代碼看上去很復雜,但實現(xiàn)的功能其實很簡單惠奸,目的是為了獲取當前文件的文件名梅誓。那么邏輯為何如此繞呢?這是由于 Rust 中的多種字符串表示導致的問題佛南,暫按不表梗掰。先去看看各個函數(shù)的定義。

Path.file_name 的定義

pub fn file_name(&self) -> Option<&OsStr>

and_then是我們常見的flat_map操作在 Rust 中的命名共虑,其目的是為了在兩個Option之間實現(xiàn)轉(zhuǎn)換愧怜。

OsStr.to_str 的定義

pub fn to_str(&self) -> Option<&str>

上面的path.file_name().and_then(|name| name.to_str())最終轉(zhuǎn)變成了Option<&str>,在其上調(diào)用Option.map_or方法并提供默認值:字符串"."妈拌。為什么要提供默認值呢拥坛?這和OsStrStr的轉(zhuǎn)換密切相關蓬蝶,當我們傳入?yún)?shù)"."時,Path.file_name返回的其實是一個None猜惋。

構(gòu)建了父級的樹狀結(jié)構(gòu)丸氛,我們需要把子級的樹狀結(jié)構(gòu)也一并完成,最終通過遞歸著摔,構(gòu)建出一棵內(nèi)存中的目錄樹缓窜。

fn children(dir: &Path) -> Vec<Entry> {
    fs::read_dir(dir)
        .expect("unable to read dir")
        .into_iter()
        .map(|e| e.expect("unable to get entry"))
        .filter(|e| is_not_hidden(&e))
        .map(|e| e.path())
        .map(|e| tree(&e))
        .collect()
}

fn is_not_hidden(entry: &DirEntry) -> bool {
    entry
         .file_name()
         .to_str()
         .map(|s| !s.starts_with("."))
         .unwrap_or(false)
}

這里也存在挺多的轉(zhuǎn)換操作,我們一一解釋谍咆。

fs::read_dir(dir).expect("unable to read dir")

使用expect是因為fs::read_dir返回的是一個Result<ReadDir>禾锤,在其上調(diào)用expect會嘗試解開其中的值,如果有錯則會拋出錯誤摹察。解開的結(jié)果類型是ReadDir恩掷,它是io::Result<DirEntry>的迭代器,也就是一個目錄下的所有類目供嚎,可以在上面調(diào)用into_iter()創(chuàng)建出可以被消費的迭代器黄娘。

.map(|e| e.expect("unable to get entry"))
.filter(|e| is_not_hidden(e))
.map(|e| e.path())
.map(|e| tree(&e))

接著,解開Result<DirEntry>之后克滴,我們把隱藏文件過濾掉逼争,因為filter接收的一個閉包,這個閉包的類型聲明是P: FnMut(&Self::Item) -> bool劝赔,所以filter接收的所有元素都是引用類型誓焦,故調(diào)用時無需需聲明成is_not_hidden(&e)

然后利用e.path()獲取每個文件的全路徑望忆,并依次交給tree去遞歸構(gòu)建罩阵。經(jīng)過treechildren兩個函數(shù)的交替遞歸,內(nèi)存中的一棵目錄樹就被構(gòu)建出來了启摄。

有了內(nèi)存中的樹狀結(jié)構(gòu)稿壁,我們接下來就可以渲染這個結(jié)構(gòu)了。具體的做法如下:

  1. 對于第一層目錄名歉备,如果它是最后一個目錄傅是,則前綴修飾為L_branch = "└── ";反之蕾羊,裝飾成 T_branch = "├── "喧笔。
  2. 對于有子目錄,如果是其父目錄是父級最后一個目錄龟再,則前綴裝飾為SPACER = " "书闸;反之,前綴裝飾成 I_branch = "│ "利凑。

邏輯如下:

fn decorate(is_last: bool, children: Vec<String>) -> Vec<String> {
    const I_BRANCH: &str = "│   ";
    const T_BRANCH: &str = "├── "; 
    const L_BRANCH: &str = "└── ";
    const   SPACER: &str = "    ";

    let prefix_first = if is_last { L_BRANCH } else { T_BRANCH };

    let prefix_rest = if is_last { SPACER } else { I_BRANCH };

    let mut first = vec![format!("{}{}", prefix_first, children[0])];

    first.extend(children[1..].iter().map(|child| format!("{}{}", prefix_rest, child)).collect::<Vec<_>>());

    first
}

這里比較好用的字符串拼接操作是format!("{}{}", &str, &str)浆劲。

渲染樹狀結(jié)構(gòu)

fn render_tree(tree: &Entry) -> Vec<String> {
    let mut names = vec![tree.name]; // error
    let children = &tree.children;
    let children: Vec<_> = children
        .iter()
        .enumerate()
        .map(|(i, child)| decorate(children.len() - 1 == i, render_tree(child)))
        .flatten()
        .collect();
    
    names.extend(children);

    names
}

這里會有編譯錯誤嫌术,錯誤信息如下:

error[e0507]: cannot move out of `tree.name` which is behind a shared reference
  --> src/main.rs:48:26
   |
48 |     let mut names = vec![tree.name];
   |                          ^^^^^^^^^ move occurs because `tree.name` has type `std::string::string`, which does not implement the `copy` trait

由于tree.name不是標量類型(Scalar Type),它沒有實現(xiàn)copy trait(見提示)牌借,又因為tree本身是復合類型(Compound Type)度气,tree.name如果發(fā)生 Move 的話,包含它的tree就有問題了膨报。為了避免發(fā)生這種情況磷籍,我們不得不去引用&tree.name。但是一旦加上引用现柠,又會出現(xiàn)類型不匹配的編譯錯誤院领。

59 |     names
   |     ^^^^^ expected struct `std::string::String`, found reference
      |
         = note: expected type `std::vec::Vec<std::string::String>`
                       found type `std::vec::Vec<&std::string::String>`

我們期待的是Vec<String>而不是Vec<&String>,所以需要重新構(gòu)建出一個String出來晒旅≌っぃ可以使用String::from(&String)方法

let mut names = vec![String::from(&tree.name)];

這樣修改下來,才能保證編譯完全通過废恋。但事實上,Rust 給我們提供了一個更加便捷的寫法

let mut names = vec![tree.name.to_owned()]

使用to_owned()表示重新拷貝了一份數(shù)據(jù)扒寄,和重新構(gòu)建一個String出來別無二致鱼鼓。

組合調(diào)用

use std::env;
use std::path::Path;
use std::fs::{self, DirEntry};

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{}", render_tree(&tree(Path::new(&args[1]))).join("\n"));
}

render_tree 返回的是Vec<String>,所以為了打印出來该编,我們將所有元素用"\n" join到一起迄本。

.
├── Cargo.toml
├── Cargo.lock
...
└── src
    └── main.rs

總結(jié)

學習下來的一些主觀感覺是 Rust 中的概念繁雜,有些地方的設計確實讓人有些迷惑课竣。再加上類型眾多(如:OsStr, String)嘉赎,代碼很難通過直覺判斷寫出,需要大量查閱文檔才能讓編譯器消停于樟。所以學習曲線相對陡峭公条。

不過,語言約束的越多迂曲,某種程度上講靶橱,對于程序員而言卻是福音。If it compiles, then it works. 的哲學理念在前路捧,學習道阻且長关霸,努力加餐飯。


提示
一般標量類型都實現(xiàn)了copy trait.

  • 所有的整型杰扫,如:u32
  • 布爾類型队寇,如:true 或 false
  • 字符類型,如:char
  • 浮點數(shù)類型章姓,如:f64
  • 當且僅當所有元素都是Copy的元組佳遣,如:(i32, i32)是Copy炭序,但是(i32, String)就不是Copy的。

于2019年9月22日


  1. https://www.reddit.com/r/rust/comments/95ky6u/why_arent_multiple_mutable_references_allowed_in/ ?

  2. http://squidarth.com/rc/rust/2018/06/04/rust-concurrency.html ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苍日,一起剝皮案震驚了整個濱河市惭聂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌相恃,老刑警劉巖辜纲,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拦耐,居然都是意外死亡耕腾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門杀糯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扫俺,“玉大人,你說我怎么就攤上這事固翰±俏常” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵骂际,是天一觀的道長疗琉。 經(jīng)常有香客問我,道長歉铝,這世上最難降的妖魔是什么盈简? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮太示,結(jié)果婚禮上柠贤,老公的妹妹穿的比我還像新娘。我一直安慰自己类缤,他們只是感情好臼勉,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呀非,像睡著了一般坚俗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岸裙,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天猖败,我揣著相機與錄音,去河邊找鬼降允。 笑死恩闻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的剧董。 我是一名探鬼主播幢尚,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼破停,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尉剩?” 一聲冷哼從身側(cè)響起真慢,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎理茎,沒想到半個月后黑界,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡皂林,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年朗鸠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片础倍。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡忆家,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沼沈,到底是詐尸還是另有隱情芽腾,我是刑警寧澤摊滔,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布眨八,位于F島的核電站页响,受9級特大地震影響栈拖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜染簇,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一青灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弹沽,春花似錦、人聲如沸丽已。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枯冈,卻和暖如春滩褥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工革骨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滑沧。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像巍实,于是被迫代替她去往敵國和親嚎货。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容