半小時(shí)入門 Rust?是的,你沒(méi)看錯(cuò)
對(duì) Rust 稍微了解的朋友,對(duì)其學(xué)習(xí)難度應(yīng)該有所耳聞。但今天這篇文章,作者試圖讓新手半小時(shí)入門 Rust。你信嗎?要不試試?
Rust 是一門系統(tǒng)編程語(yǔ)言,專注于安全:包括內(nèi)存安全、并發(fā)安全等。它支持函數(shù)式和命令式以及泛型等多種編程范式。
在這篇文章中,作者并不關(guān)注于 1 個(gè)或幾個(gè)關(guān)鍵概念,相反他希望通過(guò)代碼塊縱覽 Rust 的各種特性,包括各種關(guān)鍵詞與標(biāo)識(shí)符的意義。多讀代碼,通過(guò)代碼學(xué)習(xí)。(螃蟹哥建議:別只是讀,動(dòng)手敲代碼更重要)
01 變量
先介紹 let 變量綁定:(注意,Rust 中一般稱之為綁定)
let?x;??//?declare?"x"
x?=?42;?//?assign?42?to?"x"
你也可以寫成一行:
let?x?=?42;
通常,Rust 能夠自動(dòng)推斷出變量的類型,但你可以使用 : 來(lái)顯示指定變量的數(shù)據(jù)類型:(有時(shí)候必須顯示指定)
let?x:?i32;?//?`i32`?is?a?signed?32-bit?integer
x?=?42;
//?there's?i8,?i16,?i32,?i64,?i128
//????also?u8,?u16,?u32,?u64,?u128?for?unsigned
同樣的,你也可以寫成一行(一般建議這么寫):
let?x:?i32?=?42;
如果你聲明一個(gè)變量并在初始化之前就調(diào)用它,編譯器會(huì)報(bào)錯(cuò):
let?x;
foobar(x);?//?error:?borrow?of?possibly-uninitialized?variable:?`x`
x?=?42;
然而,這樣做完全沒(méi)問(wèn)題:
let?x;
x?=?42;
foobar(x);?//?the?type?of?`x`?will?be?inferred?from?here
下劃線 _ 是一個(gè)特殊的變量名,或者更確切地說(shuō)是「名稱的缺失」。它的基本意思是扔掉,可以理解為垃圾桶:(和 Go 中的 _ 差不多)
//?this?does?*nothing*?because?42?is?a?constant
let?_?=?42;
//?this?calls?`get_thing`?but?throws?away?its?result
let?_?=?get_thing();
以下劃線開頭的變量名是常規(guī)名稱,只是編譯器不會(huì)警告它們未被使用:
//?we?may?use?`_x`?eventually,?but?our?code?is?a?work-in-progress
//?and?we?just?wanted?to?get?rid?of?a?compiler?warning?for?now.
let?_x?=?42;
可以引入具有相同名稱的另一個(gè)綁定——這相當(dāng)于隱藏前一個(gè)變量綁定:
let?x?=?13;
let?x?=?x?+?3;
//?using?`x`?after?that?line?only?refers?to?the?second?`x`,
//?the?first?`x`?no?longer?exists.
Rust 有元組類型,你可以將其看作是“不同類型值的定長(zhǎng)集合”。
let?pair?=?('a',?17);
pair.0;?//?this?is?'a'
pair.1;?//?this?is?17
如果真的想顯示指定 pair 的數(shù)據(jù)類型,可以這么寫:
let?pair:?(char,?i32)?=?('a',?17);
元組在賦值時(shí)可以被拆解,也就是它們可以被分解成各自的字段:
let?(some_char,?some_int)?=?('a',?17);
//?now,?`some_char`?is?'a',?and?`some_int`?is?17
一個(gè)函數(shù)可以返還一個(gè)元組,這類似于多返回值:
let?(left,?right)?=?slice.split_at(middle);
當(dāng)然,在解構(gòu)一個(gè)元組時(shí),可以只分離它的一部分,這就用到了 _:
let?(_,?right)?=?slice.split_at(middle);
分號(hào)表示語(yǔ)句的結(jié)尾:
let?x?=?3;
let?y?=?5;
let?z?=?y?+?x;
不加分號(hào)意味著語(yǔ)句可以跨多行:(這些是什么意思,稍后解釋)
let?x?=?vec![1,?2,?3,?4,?5,?6,?7,?8]
????.iter()
????.map(|x|?x?+?3)
????.fold(0,?|x,?y|?x?+?y);
02 函數(shù)
fn 用來(lái)聲明一個(gè)函數(shù)。下面是一個(gè) void 函數(shù)(沒(méi)有參數(shù),沒(méi)有返回值):
fn?greet()?{
????println!("Hi?there!");
}
這是一個(gè)返回 32 位帶符號(hào)整數(shù)類型的函數(shù)。箭頭表示它的返回類型:
fn?fair_dice_roll()?->?i32?{
????4
}
花括號(hào)表示一個(gè)代碼塊,且擁有其自己的作用域:
//?This?prints?"in",?then?"out"
fn?main()?{
????let?x?=?"out";
????{
????????//?this?is?a?different?`x`
????????let?x?=?"in";
????????println!("{}",?x);
????}
????println!("{}",?x);
}
代碼塊也是表示式,最后一個(gè)表達(dá)式的值是整個(gè)代碼塊的值,以下代碼表達(dá)的意思一樣:
//?this:
let?x?=?42;
//?is?equivalent?to?this:
let?x?=?{?42?};
在一個(gè)代碼塊中,可以有多個(gè)語(yǔ)句:
let?x?=?{
????let?y?=?1;?//?first?statement
????let?z?=?2;?//?second?statement
????y?+?z?//?this?is?the?*tail*?-?what?the?whole?block?will?evaluate?to
};
這也是為什么“省略函數(shù)末尾的分號(hào)”等同于加上了 retrun,以下是等價(jià)的:
fn?fair_dice_roll()?->?i32?{
????return?4;
}
fn?fair_dice_roll()?->?i32?{
????4
}
if 條件語(yǔ)句也是表達(dá)式:
fn?fair_dice_roll()?->?i32?{
????if?feeling_lucky?{
????????6
????}?else?{
????????4
????}
}
match 也是一個(gè)表達(dá)式:
fn?fair_dice_roll()?->?i32?{
????match?feeling_lucky?{
????????true?=>?6,
????????false?=>?4,
????}
}
Dots(點(diǎn)號(hào)) 通常用于訪問(wèn)某個(gè)對(duì)象的字段:
let?a?=?(10,?20);
a.0;?//?this?is?10
let?amos?=?get_some_struct();
amos.nickname;?//?this?is?"fasterthanlime"
或者調(diào)用對(duì)象(值)的方法:
let?nick?=?"fasterthanlime";
nick.len();?//?this?is?14
雙冒號(hào)(::)與此類似,但它是對(duì)命名空間進(jìn)行操作。
在這個(gè)例子中,std 是一個(gè) crate (~ a library),cmp 是一個(gè) module(~ a source file),而 min 是個(gè)函數(shù):
let?least?=?std::cmp::min(3,?8);?//?this?is?3
use 指令可用于從其他命名空間中“引入作用域(scope)”名稱:
use?std::cmp::min;
let?least?=?min(7,?1);?//?this?is?1
在 use 指令中,花括號(hào)還有另一個(gè)含義:“globs”。如果我們想同時(shí)導(dǎo)入 min 和 max,可以這么做:
//?this?works:
use?std::cmp::min;
use?std::cmp::max;
//?this?also?works:
use?std::cmp::{min,?max};
//?this?also?works!
use?std::{cmp::min,?cmp::max};
通配符(*)允許從命名空間導(dǎo)入符號(hào):
//?this?brings?`min`?and?`max`?in?scope,?and?many?other?things
use?std::cmp::*;
類型也相當(dāng)于命名空間,通過(guò) :: 調(diào)用其上的方法,以下兩種方式等價(jià):
let?x?=?"amos".len();?//?this?is?4
let?x?=?str::len("amos");?//?this?is?also?4
str 是基本數(shù)據(jù)類型(primitive type),但在默認(rèn)情況下,許多非基本數(shù)據(jù)類型也在當(dāng)前作用域中(即不需要手動(dòng) use 導(dǎo)入)。
//?`Vec`?is?a?regular?struct,?not?a?primitive?type
let?v?=?Vec::new();
//?this?is?exactly?the?same?code,?but?with?the?*full*?path?to?`Vec`
let?v?=?std::vec::Vec::new();
為什么可以這么做呢?為了方便,Rust 在每個(gè)模塊的開頭都插入了以下代碼(我認(rèn)為類似 Java 默認(rèn)導(dǎo)入 java.lang 包):
use?std::prelude::v1::*;
以上代碼會(huì)以此導(dǎo)出許多標(biāo)識(shí)符,比如 Vec、String、Option、Result。
03 結(jié)構(gòu)體
使用 struct 關(guān)鍵字聲明結(jié)構(gòu)體:
struct?Vec2?{
????x:?f64,?//?64-bit?floating?point,?aka?"double?precision"
????y:?f64,
}
可以使用結(jié)構(gòu)語(yǔ)句初始化:
let?v1?=?Vec2?{?x:?1.0,?y:?3.0?};
let?v2?=?Vec2?{?y:?2.0,?x:?4.0?};
//?the?order?does?not?matter,?only?the?names?do
有一個(gè)快捷方式可以從另一個(gè)結(jié)構(gòu)體初始化本結(jié)構(gòu)體的其余字段:
let?v3?=?Vec2?{
????x:?14.0,
????..v2
};
這就是所謂的“結(jié)構(gòu)更新語(yǔ)法”,只能發(fā)生在最后一個(gè)位置,而且不能在其后面再跟一個(gè)逗號(hào)。
注意其余字段可以表示所有字段:
let?v4?=?Vec2?{?..v3?};
結(jié)構(gòu)體與元組一樣,可以被解構(gòu)。就像是一個(gè)有效的 let 模式:
let?(left,?right)?=?slice.split_at(middle);
let?v?=?Vec2?{?x:?3.0,?y:?6.0?};
let?Vec2?{?x,?y?}?=?v;
//?`x`?is?now?3.0,?`y`?is?now?`6.0`
還可以這樣:
let?Vec2?{?x,?..?}?=?v;
//?this?throws?away?`v.y`
let 模式可以作為 if 的條件使用:
struct?Number?{
????odd:?bool,
????value:?i32,
}
fn?main()?{
????let?one?=?Number?{?odd:?true,?value:?1?};
????let?two?=?Number?{?odd:?false,?value:?2?};
????print_number(one);
????print_number(two);
}
fn?print_number(n:?Number)?{
????if?let?Number?{?odd:?true,?value?}?=?n?{
????????println!("Odd?number:?{}",?value);
????}?else?if?let?Number?{?odd:?false,?value?}?=?n?{
????????println!("Even?number:?{}",?value);
????}
}
//?this?prints:
//?Odd?number:?1
//?Even?number:?2
多分支的 match 也是一種模式,就像 if let:
fn?print_number(n:?Number)?{
????match?n?{
????????Number?{?odd:?true,?value?}?=>?println!("Odd?number:?{}",?value),
????????Number?{?odd:?false,?value?}?=>?println!("Even?number:?{}",?value),
????}
}
//?this?prints?the?same?as?before
match 必須是囊括所有情況的的:至少需要匹配一個(gè)分支(Rust 中叫做 arm)。
fn?print_number(n:?Number)?{
????match?n?{
????????Number?{?value:?1,?..?}?=>?println!("One"),
????????Number?{?value:?2,?..?}?=>?println!("Two"),
????????Number?{?value,?..?}?=>?println!("{}",?value),
????????//?if?that?last?arm?didn't?exist,?we?would?get?a?compile-time?error
????}
}
如果實(shí)在沒(méi)法包含所有情況,可以增加一個(gè) _ 來(lái)捕獲所有其他情況,類似其他語(yǔ)言 switch 的 default:
fn?print_number(n:?Number)?{
????match?n.value?{
????????1?=>?println!("One"),
????????2?=>?println!("Two"),
????????_?=>?println!("{}",?n.value),
????}
}
你可以在自定義類型上聲明方法:
struct?Number?{
????odd:?bool,
????value:?i32,
}
impl?Number?{
????fn?is_strictly_positive(self)?->?bool?{
????????self.value?>?0
????}
}
然后像平常一樣使用:
fn?main()?{
????let?minus_two?=?Number?{
????????odd:?false,
????????value:?-2,
????};
????println!("positive??{}",?minus_two.is_strictly_positive());
????//?this?prints?"positive??false"
}
默認(rèn)情況下,聲明變量后它是不可變的,比如以下 odd 不能被重新賦值:
fn?main()?{
????let?n?=?Number?{
????????odd:?true,
????????value:?17,
????};
????n.odd?=?false;?//?error:?cannot?assign?to?`n.odd`,
???????????????????//?as?`n`?is?not?declared?to?be?mutable
}
整個(gè)結(jié)構(gòu)體值也不能二次賦值:
fn?main()?{
????let?n?=?Number?{
????????odd:?true,
????????value:?17,
????};
????n?=?Number?{
????????odd:?false,
????????value:?22,
????};?//?error:?cannot?assign?twice?to?immutable?variable?`n`
}
關(guān)鍵字 mut 可以將變量聲明變?yōu)榭勺兊模?/p>
fn?main()?{
????let?mut?n?=?Number?{
????????odd:?true,
????????value:?17,
????}
????n.value?=?19;?//?all?good
}
04 trait(特征)
Traits(特征) 描述的是多種數(shù)據(jù)類型的共有的東西:(可以類比為其他語(yǔ)言的接口)
trait?Signed?{
????fn?is_strictly_negative(self)?->?bool;
}
我們可以這么實(shí)現(xiàn):
可以對(duì)外部類型實(shí)現(xiàn)自定義的 trait; 可以對(duì)自定義類型實(shí)現(xiàn)外部 trait; 不允許對(duì)外部類型實(shí)現(xiàn)外部 trait;
這些被稱為“孤兒法則”。你如果有點(diǎn)暈,進(jìn)一步解釋一下:
當(dāng)你為某類型實(shí)現(xiàn)某 trait 的時(shí)候,必須要求類型或者 trait 至少有一個(gè)是在當(dāng)前 crate 中定義的。你不能為第三方的類型實(shí)現(xiàn)第三方的 trait。
此外,這時(shí)記得讓 trait 的方法可訪問(wèn)(公開)。
可以在我們上面定義的類型(Number)上實(shí)現(xiàn) trait:
impl?Signed?for?Number?{
????fn?is_strictly_negative(self)?->?bool?{
????????self.value?0
????}
}
fn?main()?{
????let?n?=?Number?{?odd:?false,?value:?-44?};
????println!("{}",?n.is_strictly_negative());?//?prints?"true"
}
我們對(duì)外部類型(foreign type)實(shí)現(xiàn)我們定義的 trait:(這里的外部類型使用了基本類型 i32)
impl?Signed?for?i32?{
????fn?is_strictly_negative(self)?->?bool?{
????????self?0
????}
}
fn?main()?{
????let?n:?i32?=?-44;
????println!("{}",?n.is_strictly_negative());?//?prints?"true"
}
接著在我們定義的類型上實(shí)現(xiàn)一個(gè)外部 trait:
//?the?`Neg`?trait?is?used?to?overload?`-`,?the
//?unary?minus?operator.
impl?std::ops::Neg?for?Number?{
????type?Output?=?Number;
????fn?neg(self)?->?Number?{
????????Number?{
????????????value:?-self.value,
????????????odd:?self.odd,
????????}????????
????}
}
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?987?};
????let?m?=?-n;?//?this?is?only?possible?because?we?implemented?`Neg`
????println!("{}",?m.value);?//?prints?"-987"
}
impl 代碼塊通常會(huì)對(duì)應(yīng)一個(gè)類型,所以在 impl 內(nèi),Self 就表示該類型:
impl?std::ops::Neg?for?Number?{
????type?Output?=?Self;
????fn?neg(self)?->?Self?{
????????Self?{
????????????value:?-self.value,
????????????odd:?self.odd,
????????}
????}
}
有一些 trait 只是作為標(biāo)記:它們并不是說(shuō)某個(gè)類型實(shí)現(xiàn)了某些方法,只是表明某些東西能通過(guò)類型完成。例如,i32 實(shí)現(xiàn)了 Copy trait,那么以下代碼就是可行的:(即 i32 是一個(gè) Copy)
fn?main()?{
????let?a:?i32?=?15;
????let?b?=?a;?//?`a`?is?copied
????let?c?=?a;?//?`a`?is?copied?again
}
下面的代碼也是能運(yùn)行的:
fn?print_i32(x:?i32)?{
????println!("x?=?{}",?x);
}
fn?main()?{
????let?a:?i32?=?15;
????print_i32(a);?//?`a`?is?copied
????print_i32(a);?//?`a`?is?copied?again
}
但是 Number 的結(jié)構(gòu)體并不是一個(gè) Copy,所以下面的代碼會(huì)報(bào)錯(cuò):
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????let?m?=?n;?//?`n`?is?moved?into?`m`
????let?o?=?n;?//?error:?use?of?moved?value:?`n`
}
同樣下面的代碼也不會(huì)正常:
fn?print_number(n:?Number)?{
????println!("{}?number?{}",?if?n.odd?{?"odd"?}?else?{?"even"?},?n.value);
}
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????print_number(n);?//?`n`?is?moved
????print_number(n);?//?error:?use?of?moved?value:?`n`
}
但如果 print _ number 采用不可變引用,那么它就可以正常工作:
fn?print_number(n:?&Number)?{
????println!("{}?number?{}",?if?n.odd?{?"odd"?}?else?{?"even"?},?n.value);
}
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????print_number(&n);?//?`n`?is?borrowed?for?the?time?of?the?call
????print_number(&n);?//?`n`?is?borrowed?again
}
如果一個(gè)函數(shù)接受一個(gè)可變的引用,它也可以工作,但前提是我們的變量綁定也是可變的。
fn?invert(n:?&mut?Number)?{
????n.value?=?-n.value;
}
fn?print_number(n:?&Number)?{
????println!("{}?number?{}",?if?n.odd?{?"odd"?}?else?{?"even"?},?n.value);
}
fn?main()?{
????//?this?time,?`n`?is?mutable
????let?mut?n?=?Number?{?odd:?true,?value:?51?};
????print_number(&n);
????invert(&mut?n);?//?`n?is?borrowed?mutably?-?everything?is?explicit
????print_number(&n);
}
trait 方法也可以通過(guò) self 進(jìn)行引用或可變引用:
impl?std::clone::Clone?for?Number?{
????fn?clone(&self)?->?Self?{
????????Self?{?..*self?}
????}
}
當(dāng)調(diào)用 trait 方法時(shí),接收方隱式地被借用:
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????let?mut?m?=?n.clone();
????m.value?+=?100;
????
????print_number(&n);
????print_number(&m);
}
沒(méi)理解什么意思?以下是等價(jià)的,你該理解了:
let?m?=?n.clone();
let?m?=?std::clone::Clone::clone(&n);
像 Copy 這樣的標(biāo)記特征沒(méi)有方法:
//?note:?`Copy`?requires?that?`Clone`?is?implemented?too
impl?std::clone::Clone?for?Number?{
????fn?clone(&self)?->?Self?{
????????Self?{?..*self?}
????}
}
impl?std::marker::Copy?for?Number?{}
現(xiàn)在 Clone 仍然正常:
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????let?m?=?n.clone();
????let?o?=?n.clone();
}
但是 Number 值將不再被移動(dòng):
fn?main()?{
????let?n?=?Number?{?odd:?true,?value:?51?};
????let?m?=?n;?//?`m`?is?a?copy?of?`n`
????let?o?=?n;?//?same.?`n`?is?neither?moved?nor?borrowed.
}
有些特性非常常見(jiàn),它們可以通過(guò) derive 屬性自動(dòng)實(shí)現(xiàn):
#[derive(Clone,?Copy)]
struct?Number?{
????odd:?bool,
????value:?i32,
}
//?this?expands?to?`impl?Clone?for?Number`?and?`impl?Copy?for?Number`?blocks.
05 泛型
函數(shù)支持泛型:
fn?foobar(arg:?T)?{
????//?do?something?with?`arg`
}
它們可以有多個(gè)類型參數(shù),這些參數(shù)可以在函數(shù)的聲明和函數(shù)體中使用,而不是具體類型:
fn?foobar(left:?L,?right:?R)?{
????//?do?something?with?`left`?and?`right`
}
類型參數(shù)通常有約束,因此你可以實(shí)際使用它們。
最簡(jiǎn)單的限制是特征(trait)名:
fn?print(value:?T)?{
????println!("value?=?{}",?value);
}
fn?printDebug>(value:?T)?{
????println!("value?=?{:?}",?value);
}
類型參數(shù)約束有一個(gè)更長(zhǎng)的語(yǔ)法:
fn?print(value:?T)
where
????T:?Display,
{
????println!("value?=?{}",?value);
}
約束可能更復(fù)雜:它們可能需要一個(gè)類型參數(shù)來(lái)實(shí)現(xiàn)多個(gè) traits:
use?std::fmt::Debug;
fn?compare(left:?T,?right:?T)
where
????T:?Debug?+?PartialEq,
{
????println!("{:?}?{}?{:?}",?left,?if?left?==?right?{?"=="?}?else?{?"!="?},?right);
}
fn?main()?{
????compare("tea",?"coffee");
????//?prints:?"tea"?!=?"coffee"
}
泛型函數(shù)可以看作是命名空間,包含無(wú)數(shù)具有不同具體類型的函數(shù)。
與 crates、modules 和 類型一樣,泛型函數(shù)可以通過(guò) :: 指定具體類型:
fn?main()?{
????use?std::any::type_name;
????println!("{}",?type_name::<i32>());?//?prints?"i32"
????println!("{}",?type_name::<(f64,?char)>());?//?prints?"(f64,?char)"
}
這就是所謂的 turbofish 語(yǔ)法,因?yàn)?::<> 看起來(lái)像一條魚。
結(jié)構(gòu)體也可以是泛型:
struct?Pair?{
????a:?T,
????b:?T,
}
fn?print_type_name(_val:?&T)?{
????println!("{}",?std::any::type_name::());
}
fn?main()?{
????let?p1?=?Pair?{?a:?3,?b:?9?};
????let?p2?=?Pair?{?a:?true,?b:?false?};
????print_type_name(&p1);?//?prints?"Pair"
????print_type_name(&p2);?//?prints?"Pair"
}
標(biāo)準(zhǔn)庫(kù)類型 Vec(堆分配數(shù)組)是泛型的:
fn?main()?{
????let?mut?v1?=?Vec::new();
????v1.push(1);
????let?mut?v2?=?Vec::new();
????v2.push(false);
????print_type_name(&v1);?//?prints?"Vec"
????print_type_name(&v2);?//?prints?"Vec"
}
06 宏
說(shuō)到 Vec,它附帶了一個(gè)宏 “Vec 字面值”。
fn?main()?{
????let?v1?=?vec![1,?2,?3];
????let?v2?=?vec![true,?false,?true];
????print_type_name(&v1);?//?prints?"Vec"
????print_type_name(&v2);?//?prints?"Vec"
}
所有的 name!(),name![] 或 name!{} 都是在調(diào)用宏。宏最終會(huì)展開為常規(guī)代碼。
所以,println 也是一個(gè)宏。
fn?main()?{
????println!("{}",?"Hello?there!");
}
這個(gè)會(huì)展開為和下面有相同效果的代碼:
fn?main()?{
????use?std::io::{self,?Write};
????io::stdout().lock().write_all(b"Hello?there!\n").unwrap();
}
panic 也是一個(gè)宏。如果調(diào)用了它,會(huì)使用錯(cuò)誤消息以及錯(cuò)誤的文件名/行號(hào)強(qiáng)制停止執(zhí)行:
fn?main()?{
????panic!("This?panics");
}
//?output:?thread?'main'?panicked?at?'This?panics',?src/main.rs:3:5
有些方法也會(huì)引起 panic。例如,Option 類型可以包含某些內(nèi)容,也可以不包含任何內(nèi)容。如果調(diào)用 Unwrap(),但它不包含任何內(nèi)容,就會(huì) panic:
fn?main()?{
????let?o1:?Option<i32>?=?Some(128);
????o1.unwrap();?//?this?is?fine
????let?o2:?Option<i32>?=?None;
????o2.unwrap();?//?this?panics!
}
//?output:?thread?'main'?panicked?at?'called?`Option::unwrap()`?on?a?`None`?value',?src/libcore/option.rs:378:21
07 枚舉
Option 不是結(jié)構(gòu)體,而是帶有兩個(gè)變體的枚舉:
enum?Option?{
????None,
????Some(T),
}
impl?Option?{
????fn?unwrap(self)?->?T?{
????????//?enums?variants?can?be?used?in?patterns:
????????match?self?{
????????????Self::Some(t)?=>?t,
????????????Self::None?=>?panic!(".unwrap()?called?on?a?None?option"),
????????}
????}
}
use?self::Option::{None,?Some};
fn?main()?{
????let?o1:?Option<i32>?=?Some(128);
????o1.unwrap();?//?this?is?fine
????let?o2:?Option<i32>?=?None;
????o2.unwrap();?//?this?panics!
}
//?output:?thread?'main'?panicked?at?'.unwrap()?called?on?a?None?option',?src/main.rs:11:27
Result 也是一個(gè)枚舉,它可以包含一些東西,或包含一個(gè)錯(cuò)誤:
enum?Result?{
????Ok(T),
????Err(E),
}
如果 Result 包含錯(cuò)誤時(shí),進(jìn)行 unwrap 操作會(huì) panic。
08 生命周期
變量綁定有一個(gè)“生命周期(lifetime)”:
fn?main()?{
????//?`x`?doesn't?exist?yet
????{
????????let?x?=?42;?//?`x`?starts?existing
????????println!("x?=?{}",?x);
????????//?`x`?stops?existing
????}
????//?`x`?no?longer?exists
}
類似地,引用也有生命周期:
fn?main()?{
????//?`x`?doesn't?exist?yet
????{
????????let?x?=?42;?//?`x`?starts?existing
????????let?x_ref?=?&x;?//?`x_ref`?starts?existing?-?it?borrows?`x`
????????println!("x_ref?=?{}",?x_ref);
????????//?`x_ref`?stops?existing
????????//?`x`?stops?existing
????}
????//?`x`?no?longer?exists
}
引用的生命周期你不能超過(guò)其借用的變量綁定的生命周期:
fn?main()?{
????let?x_ref?=?{
????????let?x?=?42;
????????&x
????};
????println!("x_ref?=?{}",?x_ref);
????//?error:?`x`?does?not?live?long?enough
}
一個(gè)變量綁定可以被多次借用:
fn?main()?{
????let?x?=?42;
????let?x_ref1?=?&x;
????let?x_ref2?=?&x;
????let?x_ref3?=?&x;
????println!("{}?{}?{}",?x_ref1,?x_ref2,?x_ref3);
}
在被借用期間,變量綁定不能被修改:
fn?main()?{
????let?mut?x?=?42;
????let?x_ref?=?&x;
????x?=?13;
????println!("x_ref?=?{}",?x_ref);
????//?error:?cannot?assign?to?`x`?because?it?is?borrowed
}
當(dāng)存在不可變借用時(shí),變量不能再次作為可變借用。
fn?main()?{
????let?mut?x?=?42;
????let?x_ref1?=?&x;
????let?x_ref2?=?&mut?x;
????//?error:?cannot?borrow?`x`?as?mutable?because?it?is?also?borrowed?as?immutable
????println!("x_ref1?=?{}",?x_ref1);
}
函數(shù)參數(shù)中的引用也有生命周期:
fn?print(x:?&i32)?{
????//?`x`?is?borrowed?(from?the?outside)?for?the
????//?entire?time?this?function?is?called.
}
帶有引用參數(shù)的函數(shù)可以使用具有不同生命期的 borrows 來(lái)調(diào)用,所以:
所有參數(shù)是引用的函數(shù),它們實(shí)際上都是泛型函數(shù); 生命周期就是泛型參數(shù);
生命周期名以單引號(hào) ' 開頭:
//?elided?(non-named)?lifetimes:
fn?print(x:?&i32)?{}
//?named?lifetimes:
fn?print<'a>(x:?&'a?i32)?{}
這允許返回的引用的生命周期依賴于參數(shù)的生命周期:
struct?Number?{
????value:?i32,
}
fn?number_value<'a>(num:?&'a?Number)?->?&'a?i32?{
????&num.value
}
fn?main()?{
????let?n?=?Number?{?value:?47?};
????let?v?=?number_value(&n);
????//?`v`?borrows?`n`?(immutably),?thus:?`v`?cannot?outlive?`n`.
????//?While?`v`?exists,?`n`?cannot?be?mutably?borrowed,?mutated,?moved,?etc.
}
當(dāng)只有一個(gè)輸入生命周期時(shí),它不需要命名,所有的東西都具有相同的生命周期,因此下面的兩個(gè)函數(shù)是等價(jià)的:
fn?number_value<'a>(num:?&'a?Number)?->?&'a?i32?{
????&num.value
}
fn?number_value(num:?&Number)?->?&i32?{
????&num.value
}
當(dāng)有引用字段時(shí),結(jié)構(gòu)體也是基于生命周期的泛型:
struct?NumRef<'a>?{
????x:?&'a?i32,
}
fn?main()?{
????let?x:?i32?=?99;
????let?x_ref?=?NumRef?{?x:?&x?};
????//?`x_ref`?cannot?outlive?`x`,?etc.
}
同樣的代碼,但是有一個(gè)額外的函數(shù):
struct?NumRef<'a>?{
????x:?&'a?i32,
}
fn?as_num_ref<'a>(x:?&'a?i32)?->?NumRef<'a>?{
????NumRef?{?x:?&x?}
}
fn?main()?{
????let?x:?i32?=?99;
????let?x_ref?=?as_num_ref(&x);
????//?`x_ref`?cannot?outlive?`x`,?etc.
}
相同的代碼,但省略了生命期:
struct?NumRef<'a>?{
????x:?&'a?i32,
}
fn?as_num_ref(x:?&i32)?->?NumRef<'_>?{
????NumRef?{?x:?&x?}
}
fn?main()?{
????let?x:?i32?=?99;
????let?x_ref?=?as_num_ref(&x);
????//?`x_ref`?cannot?outlive?`x`,?etc.
}
Impl 塊也可以是基于生命周期的泛型:
impl<'a>?NumRef<'a>?{
????fn?as_i32_ref(&'a?self)?->?&'a?i32?{
????????self.x
????}
}
fn?main()?{
????let?x:?i32?=?99;
????let?x_num_ref?=?NumRef?{?x:?&x?};
????let?x_i32_ref?=?x_num_ref.as_i32_ref();
????//?neither?ref?can?outlive?`x`
}
但你也可以在這里省略:
impl<'a>?NumRef<'a>?{
????fn?as_i32_ref(&self)?->?&i32?{
????????self.x
????}
}
如果你從來(lái)不需要這個(gè)名字,你可以省去更多:
impl?NumRef<'_>?{
????fn?as_i32_ref(&self)?->?&i32?{
????????self.x
????}
}
有一個(gè)特殊的生命周期,名為 'static,它對(duì)整個(gè)程序的生命周期都有效。
字符串字面量就是 'static:
struct?Person?{
????name:?&'static?str,
}
fn?main()?{
????let?p?=?Person?{
????????name:?"fasterthanlime",
????};
}
但非字面量字符串并不是 static 的:
struct?Person?{
????name:?&'static?str,
}
fn?main()?{
????let?name?=?format!("fasterthan{}",?"lime");
????let?p?=?Person?{?name:?&name?};
????//?error:?`name`?does?not?live?long?enough
}
在最后一個(gè)示例中,局部變量 name 并不是 ?&'static str,而是 String。它是動(dòng)態(tài)分配的,并且會(huì)在不需要釋放。它的生命周期小于整個(gè)程序(即使它碰巧在 main 中)。
要在 Person 中存儲(chǔ)非 'static 字符串,它需要:
A)基于生命周期的泛型:
struct?Person<'a>?{
????name:?&'a?str,
}
fn?main()?{
????let?name?=?format!("fasterthan{}",?"lime");
????let?p?=?Person?{?name:?&name?};
????//?`p`?cannot?outlive?`name`
}
或
B)取得字符串所有權(quán)
struct?Person?{
????name:?String,
}
fn?main()?{
????let?name?=?format!("fasterthan{}",?"lime");
????let?p?=?Person?{?name:?name?};
????//?`name`?was?moved?into?`p`,?their?lifetimes?are?no?longer?tied.
}
在 struct literal 中,當(dāng)一個(gè)字段被設(shè)置為同名的變量綁定時(shí):
let?p?=?Person?{?name:?name?};
它可以這樣簡(jiǎn)寫:
let?p?=?Person?{?name?};
09 所有權(quán)
對(duì)于 Rust 中的許多類型,存在有所有權(quán)和沒(méi)有所有權(quán)之分:
Strings:String 是有所有權(quán)的;$str 是一個(gè)引用; Paths:PathBuf 是有所有權(quán)的;$Path 是一個(gè)引用; Collections: Vec是有所有權(quán)的;&[T] 是一個(gè)引用;
Rust 有切片類型(slice)—— 它們是對(duì)多個(gè)連續(xù)元素的引用。
你可以借用 Vector 的一個(gè)切片,例如:
fn?main()?{
????let?v?=?vec![1,?2,?3,?4,?5];
????let?v2?=?&v[2..4];
????println!("v2?=?{:?}",?v2);
}
//?output:
//?v2?=?[3,?4]
以上并不神奇。索引操作符(foo[index])被 Index 和 IndexMut 特征重載。
.. 語(yǔ)法是 range 字面值,range 是標(biāo)準(zhǔn)庫(kù)中定義的幾個(gè)結(jié)構(gòu)體。
它們可以是半閉半開的,如果前面有 = ,那么它們就是閉區(qū)間。
fn?main()?{
????//?0?or?greater
????println!("{:?}",?(0..).contains(&100));?//?true
????//?strictly?less?than?20
????println!("{:?}",?(..20).contains(&20));?//?false
????//?20?or?less?than?20
????println!("{:?}",?(..=20).contains(&20));?//?true
????//?only?3,?4,?5
????println!("{:?}",?(3..6).contains(&4));?//?true
}
借用規(guī)則適用于切片。
fn?tail(s:?&[u8])?->?&[u8]?{
??&s[1..]?
}
fn?main()?{
????let?x?=?&[1,?2,?3,?4,?5];
????let?y?=?tail(x);
????println!("y?=?{:?}",?y);
}
這和下面的一樣:
fn?tail<'a>(s:?&'a?[u8])?->?&'a?[u8]?{
??&s[1..]?
}
以下是合法的:
fn?main()?{
????let?y?=?{
????????let?x?=?&[1,?2,?3,?4,?5];
????????tail(x)
????};
????println!("y?=?{:?}",?y);
}
但因?yàn)?[1,2,3,4,5] 是一個(gè) 'static 數(shù)組。所以,以下是非法的:
fn?main()?{
????let?y?=?{
????????let?v?=?vec![1,?2,?3,?4,?5];
????????tail(&v)
????????//?error:?`v`?does?not?live?long?enough
????};
????println!("y?=?{:?}",?y);
}
因?yàn)?Vector 是在堆分配的,而且它的生命周期是非 'static 的。
&str 實(shí)際上是切片。
fn?file_ext(name:?&str)?->?Option<&str>?{
????//?this?does?not?create?a?new?string?-?it?returns
????//?a?slice?of?the?argument.
????name.split(".").last()
}
fn?main()?{
????let?name?=?"Read?me.?Or?don't.txt";
????if?let?Some(ext)?=?file_ext(name)?{
????????println!("file?extension:?{}",?ext);
????}?else?{
????????println!("no?file?extension");
????}
}
所以,借用規(guī)則也適用它:
fn?main()?{
????let?ext?=?{
????????let?name?=?String::from("Read?me.?Or?don't.txt");
????????file_ext(&name).unwrap_or("")
????????//?error:?`name`?does?not?live?long?enough
????};
????println!("extension:?{:?}",?ext);
}
10 錯(cuò)誤處理
可能失敗的函數(shù)通常返回一個(gè) Result:
fn?main()?{
????let?s?=?std::str::from_utf8(&[240,?159,?141,?137]);
????println!("{:?}",?s);
????//?prints:?Ok("??")
????let?s?=?std::str::from_utf8(&[195,?40]);
????println!("{:?}",?s);
????//?prints:?Err(Utf8Error?{?valid_up_to:?0,?error_len:?Some(1)?})
}
如果你想在失敗的情況下 panic,你可以調(diào)用 unwrap():
fn?main()?{
????let?s?=?std::str::from_utf8(&[240,?159,?141,?137]).unwrap();
????println!("{:?}",?s);
????//?prints:?"??"
????let?s?=?std::str::from_utf8(&[195,?40]).unwrap();
????//?prints:?thread?'main'?panicked?at?'called?`Result::unwrap()`
????//?on?an?`Err`?value:?Utf8Error?{?valid_up_to:?0,?error_len:?Some(1)?}',
????//?src/libcore/result.rs:1165:5
}
或者 .expect(),它可以提供自定義錯(cuò)誤信息。
fn?main()?{
????let?s?=?std::str::from_utf8(&[195,?40]).expect("valid?utf-8");
????//?prints:?thread?'main'?panicked?at?'valid?utf-8:?Utf8Error
????//?{?valid_up_to:?0,?error_len:?Some(1)?}',?src/libcore/result.rs:1165:5
}
或者你也可以使用 match:
fn?main()?{
????match?std::str::from_utf8(&[240,?159,?141,?137])?{
????????Ok(s)?=>?println!("{}",?s),
????????Err(e)?=>?panic!(e),
????}
????//?prints???
}
也可以使用 if let:
fn?main()?{
????if?let?Ok(s)?=?std::str::from_utf8(&[240,?159,?141,?137])?{
????????println!("{}",?s);
????}
????//?prints???
}
或者你可以拋出這樣的錯(cuò)誤:
fn?main()?->?Result<(),?std::str::Utf8Error>?{
????match?std::str::from_utf8(&[240,?159,?141,?137])?{
????????Ok(s)?=>?println!("{}",?s),
????????Err(e)?=>?return?Err(e),
????}
????Ok(())
}
或者你可以使用 ? 來(lái)簡(jiǎn)潔地完成它:
fn?main()?->?Result<(),?std::str::Utf8Error>?{
????let?s?=?std::str::from_utf8(&[240,?159,?141,?137])?;
????println!("{}",?s);
????Ok(())
}
操作符 * 可用于解引用,但是訪問(wèn)字段或調(diào)用方法不需要解引用:
struct?Point?{
????x:?f64,
????y:?f64,
}
fn?main()?{
????let?p?=?Point?{?x:?1.0,?y:?3.0?};
????let?p_ref?=?&p;
????println!("({},?{})",?p_ref.x,?p_ref.y);
}
//?prints?`(1,?3)`
而且你只能在類型為 Copy 的情況下才能這么做:
struct?Point?{
????x:?f64,
????y:?f64,
}
fn?negate(p:?Point)?->?Point?{
????Point?{
????????x:?-p.x,
????????y:?-p.y,
????}
}
fn?main()?{
????let?p?=?Point?{?x:?1.0,?y:?3.0?};
????let?p_ref?=?&p;
????negate(*p_ref);
????//?error:?cannot?move?out?of?`*p_ref`?which?is?behind?a?shared?reference
}
//?now?`Point`?is?`Copy`
#[derive(Clone,?Copy)]
struct?Point?{
????x:?f64,
????y:?f64,
}
fn?negate(p:?Point)?->?Point?{
????Point?{
????????x:?-p.x,
????????y:?-p.y,
????}
}
fn?main()?{
????let?p?=?Point?{?x:?1.0,?y:?3.0?};
????let?p_ref?=?&p;
????negate(*p_ref);?//?...and?now?this?works
}
11 閉包
閉包是 Fn、 FnMut 或 FnOnce 類型的函數(shù),包含一些捕獲的上下文。
它們的參數(shù)是一對(duì)管道(|)中以逗號(hào)分隔的名稱列表。它們不需要大括號(hào),除非您希望有多個(gè)語(yǔ)句。
fn?for_each_planet(f:?F)
????where?F:?Fn(&'static?str)
{
????f("Earth");
????f("Mars");
????f("Jupiter");
}
?
fn?main()?{
????for_each_planet(|planet|?println!("Hello,?{}",?planet));
}
//?prints:
//?Hello,?Earth
//?Hello,?Mars
//?Hello,?Jupiter
借款規(guī)則也適用于它們:
fn?for_each_planet(f:?F)
????where?F:?Fn(&'static?str)
{
????f("Earth");
????f("Mars");
????f("Jupiter");
}
?
fn?main()?{
????let?greeting?=?String::from("Good?to?see?you");
????for_each_planet(|planet|?println!("{},?{}",?greeting,?planet));
????//?our?closure?borrows?`greeting`,?so?it?cannot?outlive?it
}
例如,這樣做不會(huì)奏效:
fn?for_each_planet(f:?F)
????where?F:?Fn(&'static?str)?+?'static?//?`F`?must?now?have?"'static"?lifetime
{
????f("Earth");
????f("Mars");
????f("Jupiter");
}
fn?main()?{
????let?greeting?=?String::from("Good?to?see?you");
????for_each_planet(|planet|?println!("{},?{}",?greeting,?planet));
????//?error:?closure?may?outlive?the?current?function,?but?it?borrows
????//?`greeting`,?which?is?owned?by?the?current?function
}
但這樣可以:
fn?main()?{
????let?greeting?=?String::from("You're?doing?great");
????for_each_planet(move?|planet|?println!("{},?{}",?greeting,?planet));
????//?`greeting`?is?no?longer?borrowed,?it?is?*moved*?into
????//?the?closure.
}
需要可變地借用 FnMut 才能調(diào)用它,因此一次只能調(diào)用它一次。
這是合法的:
fn?foobar(f:?F)
????where?F:?Fn(i32)?->?i32
{
????println!("{}",?f(f(2)));?
}
?
fn?main()?{
????foobar(|x|?x?*?2);
}
//?output:?8
但以下不行:
fn?foobar(mut?f:?F)
????where?F:?FnMut(i32)?->?i32
{
????println!("{}",?f(f(2)));?
????//?error:?cannot?borrow?`f`?as?mutable?more?than?once?at?a?time
}
?
fn?main()?{
????foobar(|x|?x?*?2);
}
不過(guò)下面又是合法的:
fn?foobar(mut?f:?F)
????where?F:?FnMut(i32)?->?i32
{
????let?tmp?=?f(2);
????println!("{}",?f(tmp));?
}
?
fn?main()?{
????foobar(|x|?x?*?2);
}
//?output:?8
FnMut 的存在是因?yàn)橐恍╅]包可變地借用了局部變量:
fn?foobar(mut?f:?F)
????where?F:?FnMut(i32)?->?i32
{
????let?tmp?=?f(2);
????println!("{}",?f(tmp));?
}
?
fn?main()?{
????let?mut?acc?=?2;
????foobar(|x|?{
????????acc?+=?1;
????????x?*?acc
????});
}
//?output:?24
這些閉包不能傳遞給期待 Fn 的函數(shù):
fn?foobar(f:?F)
????where?F:?Fn(i32)?->?i32
{
????println!("{}",?f(f(2)));?
}
?
fn?main()?{
????let?mut?acc?=?2;
????foobar(|x|?{
????????acc?+=?1;
????????//?error:?cannot?assign?to?`acc`,?as?it?is?a
????????//?captured?variable?in?a?`Fn`?closure.
????????//?the?compiler?suggests?"changing?foobar
????????//?to?accept?closures?that?implement?`FnMut`"
????????x?*?acc
????});
}
FnOnce 閉包只能調(diào)用一次。它們的存在是因?yàn)橐恍╅]包移出了捕獲時(shí)移動(dòng)的變量:
fn?foobar(f:?F)
????where?F:?FnOnce()?->?String
{
????println!("{}",?f());?
}
?
fn?main()?{
????let?s?=?String::from("alright");
????foobar(move?||?s);
????//?`s`?was?moved?into?our?closure,?and?our
????//?closures?moves?it?to?the?caller?by?returning
????//?it.?Remember?that?`String`?is?not?`Copy`.
}
這是自然地強(qiáng)制執(zhí)行的,因?yàn)?FnOnce 閉包需要移動(dòng)才能被調(diào)用。
以下例子是無(wú)效的:
fn?foobar(f:?F)
????where?F:?FnOnce()?->?String
{
????println!("{}",?f());?
????println!("{}",?f());?
????//?error:?use?of?moved?value:?`f`
}
而且,如果你需要有說(shuō)服力的證明我們的關(guān)閉確實(shí)會(huì)發(fā)生變化,這也是非法的:
fn?main()?{
????let?s?=?String::from("alright");
????foobar(move?||?s);
????foobar(move?||?s);
????//?use?of?moved?value:?`s`
}
不過(guò)這樣是正常的:
fn?main()?{
????let?s?=?String::from("alright");
????foobar(||?s.clone());
????foobar(||?s.clone());
}
下面是一個(gè)包含兩個(gè)參數(shù)的閉包:
fn?foobar(x:?i32,?y:?i32,?is_greater:?F)
????where?F:?Fn(i32,?i32)?->?bool
{
????let?(greater,?smaller)?=?if?is_greater(x,?y)?{
????????(x,?y)
????}?else?{
????????(y,?x)
????};
????println!("{}?is?greater?than?{}",?greater,?smaller);
}
?
fn?main()?{
????foobar(32,?64,?|x,?y|?x?>?y);
}
以下是一個(gè)閉包,但忽略了它的兩個(gè)參數(shù):
fn?main()?{
????foobar(32,?64,?|_,?_|?panic!("Comparing?is?futile!"));
}
下面是一個(gè)有點(diǎn)令人擔(dān)憂的閉包:
fn?countdown(count:?usize,?tick:?F)
????where?F:?Fn(usize)
{
????for?i?in?(1..=count).rev()?{
????????tick(i);
????}
}
?
fn?main()?{
????countdown(3,?|i|?println!("tick?{}...",?i));
}
//?output:
//?tick?3...
//?tick?2...
//?tick?1...
下面是一個(gè) toilet 閉包:
fn?main()?{
????countdown(3,?|_|?());
}
之所以這么叫是因?yàn)?|_| () 看起來(lái)像個(gè)廁所(toilet )。
任何可迭代的東西都可以在 for in 循環(huán)中使用。
我們剛剛看到一個(gè) range 被使用,但是它也適用于一個(gè) Vec:
fn?main()?{
????for?i?in?vec![52,?49,?21]?{
????????println!("I?like?the?number?{}",?i);
????}
}
或者切片:
fn?main()?{
????for?i?in?&[52,?49,?21]?{
????????println!("I?like?the?number?{}",?i);
????}
}
//?output:
//?I?like?the?number?52
//?I?like?the?number?49
//?I?like?the?number?21
或者是迭代器:
fn?main()?{
????//?note:?`&str`?also?has?a?`.bytes()`?iterator.
????//?Rust's?`char`?type?is?a?"Unicode?scalar?value"
????for?c?in?"rust".chars()?{
????????println!("Give?me?a?{}",?c);
????}
}
//?output:
//?Give?me?a?r
//?Give?me?a?u
//?Give?me?a?s
//?Give?me?a?t
即使迭代的項(xiàng)目被 filter、map 或 flat:
fn?main()?{
????for?c?in?"SuRPRISE?INbOUND"
????????.chars()
????????.filter(|c|?c.is_lowercase())
????????.flat_map(|c|?c.to_uppercase())
????{
????????print!("{}",?c);
????}
????println!();
}
//?output:?UB
你可以從函數(shù)返回一個(gè)閉包:
fn?make_tester(answer:?String)?->?impl?Fn(&str)?->?bool?{
????move?|challenge|?{
????????challenge?==?answer
????}
}
fn?main()?{
????//?you?can?use?`.into()`?to?perform?conversions
????//?between?various?types,?here?`&'static?str`?and?`String`
????let?test?=?make_tester("hunter2".into());
????println!("{}",?test("******"));
????println!("{}",?test("hunter2"));
}
你甚至可以將對(duì)某個(gè)函數(shù)參數(shù)的引用移動(dòng)到它返回的閉包中:
fn?make_tester<'a>(answer:?&'a?str)?->?impl?Fn(&str)?->?bool?+?'a?{
????move?|challenge|?{
????????challenge?==?answer
????}
}
fn?main()?{
????let?test?=?make_tester("hunter2");
????println!("{}",?test("*******"));
????println!("{}",?test("hunter2"));
}
//?output:
//?false
//?true
或者,用省略的生命周期:
fn?make_tester(answer:?&str)?->?impl?Fn(&str)?->?bool?+?'_?{
????move?|challenge|?{
????????challenge?==?answer
????}
}
總結(jié)
以上就是這個(gè) 30 分鐘教程的全部?jī)?nèi)容。作者認(rèn)為,通過(guò)以上的學(xué)習(xí),你應(yīng)該能夠閱讀網(wǎng)上找到的大部分 Rust 代碼了。
讀 Rust 代碼和寫 Rust 代碼很不一樣。一方面,你不是讀一個(gè)問(wèn)題的解決方案,而是實(shí)際解決它;另一方面,Rust 編譯器很強(qiáng)大,可以給你很多幫助。
對(duì)于這篇教程中故意的錯(cuò)誤代碼,Rust 編譯器總是能非常好的提示你,同時(shí)給你很好的建議。
原文鏈接:https://fasterthanli.me/articles/a-half-hour-to-learn-rust
Rust 編程指北編譯,非直接翻譯。
實(shí)話說(shuō),這個(gè)教程不可能 30 分鐘學(xué)完,即使學(xué)完了,肯定也沒(méi)入門。不過(guò)通過(guò)這些代碼,自己動(dòng)手實(shí)踐,對(duì)學(xué)習(xí) Rust 還是有幫助的!
推薦閱讀
