fnmain() { // return a tuple of return values letresult = swap(123, 321); println!("{} {}", result.0, result.1);
// destructure the tuple into two variables names let (a, b) = swap(result.0, result.1); println!("{} {}", a, b); }
如果没有声明函数的返回类型,它将返回一个空元组(),也称单元(unit)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
fnmake_nothing() -> () { return (); }
// the return type is implied as () fnmake_nothing2() { // this function will return () if nothing is specified to return }
fnmain() { leta = make_nothing(); letb = make_nothing2();
// Printing a debug string for a and b // Because it's hard to print nothingness println!("The value of a: {:?}", a); println!("The value of b: {:?}", b); }
match x { 0 => { println!("found zero"); } // we can match against multiple values 1 | 2 => { println!("found 1 or 2!"); } // we can match against ranges 3..=9 => { println!("found a number 3 to 9 inclusively"); } // we can bind the matched number to a variable matched_num @ 10..=100 => { println!("found {} number between 10 to 100!", matched_num); } // this is the default match that must exist if not all cases are handled _ => { println!("found something else!"); } } }
letfood = "hamburger"; letresult = match food { "hotdog" => "is hotdog", // notice the braces are optional when its just a single return expression _ => "is not hotdog", }; println!("identifying food: {}", result);
letv = { // This scope block lets us get a result without polluting function scope leta = 1; letb = 2; a + b }; println!("from block: {}", v);
// The idiomatic way to return a value in rust from a function at the end v + 4 }
不难看出,以下例子 from 方法属 String 类型的静态方法,而 len 则属 String 类型的实例 s 的实例方法
1 2 3 4 5 6
fnmain() { // Using a static method to create an instance of String lets = String::from("Hello world!"); // Using a method on the instance println!("{} is {} characters long.", s, s.len()); }
fnmain() { // SeaCreature's data is on stack letferris = SeaCreature { // String struct is also on stack, // but holds a reference to data on heap animal_type: String::from("crab"), name: String::from("Ferris"), arms: 2, legs: 4, weapon: String::from("claw"), };
letsarah = SeaCreature { animal_type: String::from("octopus"), name: String::from("Sarah"), arms: 8, legs: 0, weapon: String::from("brain"), }; println!( "{} is a {}. They have {} arms, {} legs, and a {} weapon", ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon ); println!( "{} is a {}. They have {} arms, and {} legs. They have no weapon..", sarah.name, sarah.animal_type, sarah.arms, sarah.legs ); }
元组型结构
为了便于使用,可以创建使用起来像元组的结构
1 2 3 4 5 6 7
structLocation(i32, i32);
fnmain() { // This is still a struct on a stack letloc = Location(42, 32); println!("{}, {}", loc.0, loc.1); }
match ferris.species { Species::Crab => println!("{} is a crab",ferris.name), Species::Octopus => println!("{} is a octopus",ferris.name), Species::Fish => println!("{} is a fish",ferris.name), Species::Clam => println!("{} is a clam",ferris.name), } }
fnmain() { // SeaCreature's data is on stack letferris = SeaCreature { // String struct is also on stack, // but holds a reference to data on heap species: Species::Crab, name: String::from("Ferris"), arms: 2, legs: 4, weapon: Weapon::Claw(2, Size::Small), };
match ferris.species { Species::Crab => { match ferris.weapon { Weapon::Claw(num_claws,size) => { letsize_description = match size { Size::Big => "big", Size::Small => "small" }; println!("ferris is a crab with {} {} claws", num_claws, size_description) }, _ => println!("ferris is a crab with some other weapon") } }, _ => println!("ferris is some other animal"), } }
// A partially defined struct type structBagOfHolding<T> { item: T, }
fnmain() { // Note: by using generic types here, we create compile-time created types. // Turbofish lets us be explicit. leti32_bag = BagOfHolding::<i32> { item: 42 }; letbool_bag = BagOfHolding::<bool> { item: true }; // Rust can infer types for generics too! letfloat_bag = BagOfHolding { item: 3.14 }; // Note: never put a bag of holding in a bag of holding in real life letbag_in_bag = BagOfHolding { item: BagOfHolding { item: "boom!" }, };
// A partially defined struct type structBagOfHolding<T> { // Our parameter type T can be handed to others item: Option<T>, }
fnmain() { // Note: A bag for i32, holding nothing! We have to specify the type // because otherwise Rust would not know what type of bag it is. leti32_bag = BagOfHolding::<i32> { item: None };
if i32_bag.item.is_none() { println!("there's nothing in the bag!") } else { println!("there's something in the bag!") }
if i32_bag.item.is_some() { println!("there's something in the bag!") } else { println!("there's nothing in the bag!") }
// match lets us deconstruct Option elegantly and ensure we handle all cases! match i32_bag.item { Some(v) => println!("found {} in bag!", v), None => println!("found nothing"), } }
Result
内建枚举 Result 允许我们返回一个可能“失败”的值,它也是 rust 惯用的错误处理手段
1 2 3 4
enumResult<T, E> { Ok(T), Err(E), }
由于此枚举非常常用,可以随时用枚举变种 Ok, Err 来创建它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fndo_something_that_might_fail(i:i32) ->Result<f32,String> { if i == 42 { Ok(13.0) } else { Err(String::from("this is not the right number")) } }
// match lets us deconstruct Result elegantly and ensure we handle all cases! match result { Ok(v) => println!("found {}", v), Err(e) => println!("Error: {}",e), } }
fndo_something_that_might_fail(i: i32) ->Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("this is not the right number")) } }
// Main returns no value, but could return an error! fnmain() ->Result<(), String> { letresult = do_something_that_might_fail(12);
match result { Ok(v) => println!("found {}", v), Err(_e) => { // handle this error gracefully // return a new error from main that said what happened! returnErr(String::from("something went wrong in main!")); } }
// Notice we use a unit value inside a Result Ok // to represent everything is fine Ok(()) }
优雅错误处理
为方便 Result 的使用,语言设计者为 rust 设计了一个专门的运算符 ?,以下两个语句是等价的:
fnmain() { // We can be explicit with type letmut i32_vec = Vec::<i32>::new(); // turbofish <3 i32_vec.push(1); i32_vec.push(2); i32_vec.push(3);
// But look how clever Rust is about determining the type automatically letmut float_vec = Vec::new(); float_vec.push(1.3); float_vec.push(2.3); float_vec.push(3.4);
// That's a beautiful macro! letstring_vec = vec![String::from("Hello"), String::from("World")];
// FAILURE: do_something(foo) would fail because // foo cannot be moved while mutably borrowed
// FAILURE: foo.x = 13; would fail here because // foo is not modifiable while mutably borrowed
f.x = 13; // f is dropped here because it's no longer used after this point println!("{}", foo.x); // this works now because all mutable references were dropped foo.x = 7; // move foo's ownership to a function do_something(foo); }
解引用
对于可变变量引用,可以用 * 运算符设置所引用变量的值
也可以使用 * 运算符获得所引用变量值的一份拷贝(前提是其值可拷贝)
1 2 3 4 5 6 7 8
fnmain() { letmut foo = 42; letf = &mut foo; letbar = *f; // get a copy of the owner's value *f = 13; // set the reference's owner's value println!("{}", bar); println!("{}", foo); }
传递借用数据
rust 的引用规则可总结如下:
rust 仅允许变量有一个可改变引用或多个不可改变引用
引用的生命周期不会长于拥有者
传递引用到一个函数也不会有什么问题
内存布局:
第一条规则避免了数据竞争
第二条规则避免了引用不存在数据(C 中的“悬挂指针”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
structFoo { x: i32, }
fndo_something(f: &mut Foo) { f.x += 1; // mutable reference f is dropped here }
fnmain() { letmut foo = Foo { x: 42 }; do_something(&mut foo); // because all mutable references are dropped within // the function do_something, we can create another. do_something(&mut foo); // foo is dropped here }
从引用创建引用
可以从引用创建引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
structFoo { x: i32, }
fndo_something(a: &Foo) -> &i32 { return &a.x; }
fnmain() { letmut foo = Foo { x: 42 }; letx = &mut foo.x; *x = 13; // x is dropped here allow us to create a non-mutable reference lety = do_something(&foo); println!("{}", y); // y is dropped here // foo is dropped here }
// the parameter foo and return value share the same lifetime fndo_something<'a>(foo: &'a Foo) -> &'ai32 { return &foo.x; // 译注:foo就不会在这里被丢弃了? }
fnmain() { letmut foo = Foo { x: 42 }; letx = &mut foo.x; *x = 13; // x is dropped here, allowing us to create a non-mutable reference lety = do_something(&foo); println!("{}", y); // y is dropped here // foo is dropped here }
译注:原文:Lifetime specifiers allow us to be explicit with certain scenarios the compiler cannot resolve itself by distinguishing all of a function signature component’s lifetimes.
// foo_b and the return value share the same lifetime // foo_a has an unrelated lifetime fndo_something<'a, 'b>(foo_a: &'a Foo, foo_b: &'b Foo) -> &'bi32 { println!("{}", foo_a.x); println!("{}", foo_b.x); return &foo_b.x; }
fnmain() { letfoo_a = Foo { x: 42 }; letfoo_b = Foo { x: 12 }; letx = do_something(&foo_a, &foo_b); // foo_a is dropped here because only foo_b's lifetime exist beyond here println!("{}", x); // x is dropped here // foo_b is dropped here }
fnmain() { // static variables can also be scoped to a function staticmut SECRET: &'staticstr = "swordfish";
// string literals have a 'static lifetime letmsg: &'staticstr = "Hello World!"; letp: &'staticf64 = &PI; println!("{} {}", msg, p);
// You can break some rules, but you must be explicit unsafe { // we can set SECRET to a string literal because it is also `static SECRET = "abracadabra"; println!("{}", SECRET); } }
fnmain() { leta: &'staticstr = "Ferris says:\t\"hello\""; println!("{}",a); }
多行字符串
rust 的字符串默认就是多行的,如果不想要行尾的换行符,就在行尾添加一个 \
1 2 3 4 5 6 7 8 9 10 11 12
fnmain() { lethaiku: &'staticstr = " I write, erase, rewrite Erase again, and then A poppy blooms. - Katsushika Hokusai"; println!("{}", haiku); println!("hello \ world") // notice that the spacing before w is ignored }
原始字符串
原始字符串以 r#" 开始,"# 结束,编译器将不加修改地保留中间所有字符
1 2 3 4 5 6 7 8
fnmain() { leta: &'staticstr = r#" <div class="advice"> Raw strings are useful for some situations. </div> "#; println!("{}", a); }
从文件加载字符串
可以使用 include_str! 宏从文件加载字符串
1
lethello_html = include_str!("hello.html");
字符串切片
字符串切片是对内存中一段有效的 utf-8 字节序列的引用
&str 常见的方法有:
len — 返回字符串占用的字节个数
starts_with, ends_with — 判断字符串是否以特定的序列开始/结尾
is_empty — 判断字符串是否为空
find — 查找字符串,返回 Option<usize>,指代待查字符串所在的第一个位置
1 2 3 4 5 6 7 8 9
fnmain() { leta = "hi 🦀"; println!("{}", a.len()); letfirst_word = &a[0..2]; letsecond_word = &a[3..7]; // let half_crab = &a[3..5]; FAILS // Rust does not accept slices of invalid unicode characters println!("{} {}", first_word, second_word); }
fnmain() { // collect the characters as a vector of char letchars = "hi 🦀".chars().collect::<Vec<char>>(); println!("{}", chars.len()); // should be 4 // since chars are 4 bytes we can convert to u32 println!("{}", chars[3] asu32); }
fnmain() { // say_it_loud can borrow &'static str as a &str say_it_loud("hello"); // say_it_loud can also borrow String as a &str say_it_loud(&String::from("goodbye")); }
fnmain() { leta: [u8; 4] = [86, 14, 73, 64]; // this is a raw pointer. Getting the memory address // of something as a number is totally safe letpointer_a = &a as *constu8asusize; println!("Data memory location: {}", pointer_a); // Turning our number into a raw pointer to a f32 is // also safe to do. letpointer_b = pointer_a as *constf32; letb = unsafe { // This is unsafe because we are telling the compiler // to assume our pointer is a valid f32 and // dereference it's value into the variable b. // Rust has no way to verify this assumption is true. *pointer_b }; println!("I swear this is a pie! {}", b); }
unsafe { // allocate and save the memory location as a number letptr = alloc(layout) as *mutu8; // use pointer math and write a few // u8 values to memory ptr.write(86); ptr.add(1).write(14); ptr.add(2).write(73); ptr.add(3).write(64);
Pie { secret_recipe: ptr asusize } } } } implDerefforPie { typeTarget = f32; fnderef(&self) -> &f32 { // interpret secret_recipe pointer as a f32 raw pointer letpointer = self.secret_recipe as *constf32; // dereference it into a return value &f32 unsafe { &*pointer } } } fnmain() { letp = Pie::new(); // "make a pie" by dereferencing our // Pie struct smart pointer println!("{:?}", *p); }
堆上分配的内存
Box 是一个让我们能够将数据从栈内存移动到堆内存的智能指针
解引用它让我们可以像访问原始类型一样访问在堆上分配的数据
1 2 3 4 5 6 7 8 9 10 11 12
structPie;
implPie { fneat(&self) { println!("tastes better on the heap!") } }
implPie { fneat(&mutself) { println!("tastes better on the heap!"); self.slices -= 1; } }
fnmain() { // RefCell validates memory safety at runtime // notice: pie_cell is not mut! letpie_cell = RefCell::new(Pie{slices:8}); { // but we can borrow mutable references! letmut mut_ref_pie = pie_cell.borrow_mut(); mut_ref_pie.eat(); mut_ref_pie.eat(); // mut_ref_pie is dropped at end of scope } // now we can borrow immutably once our mutable reference drops letref_pie = pie_cell.borrow(); println!("{} slices left",ref_pie.slices); }
注意:上面 pie_cell 本身是不可变的,但我们可以向其借用 Pie 的可变使用权
在线程间共享
Mutex 是常常被智能指针拥有的一个容器类型,它保存数据,并允许被不可变或可变地借用。
它强制同时只有一个 CPU 线程能访问其中的数据,来防止借用被破坏,其他 CPU 线程在一个线程借用期间必须等待。
Mutex 是 rust 并发编程的一个基本类型
还有一个和 Rc 类似的特殊的智能指针 Arc,它和 Rc 不同的是会线程安全地维护引用计数,它通常被用来多次引用同一个 Mutex(译注:原文:. It’s often used to have many references to the same Mutex.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use std::sync::Mutex;
structPie;
implPie { fneat(&self) { println!("only I eat the pie right now!"); } }
fnmain() { letmutex_pie = Mutex::new(Pie); // let's borrow a locked immutable reference of pie // we have to unwrap the result of a lock // because it might fail letref_pie = mutex_pie.lock().unwrap(); ref_pie.eat(); // locked reference drops here, and mutex protected value can be used by someone else }
组合智能指针
智能指针看似是有穷的,不过它们可以组合成更强大的功能
Rc<Vec<Foo>> - 允许多个智能指针的克隆借用堆上同一个不变数据结构 Foo 的 Vec
Rc<RefCell<Foo>> - 允许多个智能指针不可变/可变地借用同一个 Foo
Arc<Mutex<Foo>> - 允许多个智能指针 CPU 线程互斥地不可变/可变地短暂借用同一个 Foo
implSeaCreature { fneat(&self) { // use smart pointer to pie for a mutable borrow letmut p = self.pie.borrow_mut(); // take a bite! p.eat_slice(&self.name); } }
fnmain() { letpie = Rc::new(RefCell::new(Pie { slices: 8 })); // ferris and sarah are given clones of smart pointer to pie letferris = SeaCreature { name: String::from("ferris"), pie: pie.clone(), }; letsarah = SeaCreature { name: String::from("sarah"), pie: pie.clone(), }; ferris.eat(); sarah.eat();
// This macro removes this inline module when Rust // is not in test mode. #[cfg(test)] mod tests { // Notice that we don't immediately get access to the // parent module. We must be explicit. use super::*;