Options: -q, --quiet Do not print cargo log messages --registry <REGISTRY> Registry to use --vcs <VCS> Initialize a new repository for the given version control system (git, hg, pijul, or fossil) or do not initialize any version control at all (none), overriding a global configuration. [possible values: git, hg, pijul, fossil, none] --bin Use a binary (application) template [default] -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --lib Use a library template --color <WHEN> Coloring: auto, always, never --edition <YEAR> Edition to setfor the crate generated [possible values: 2015, 2018, 2021] --frozen Require Cargo.lock and cache are up to date --name <NAME> Set the resulting package name, defaults to the directory name --locked Require Cargo.lock is up to date --offline Run without accessing the network --config <KEY=VALUE> Override a configuration value -Z <FLAG> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help'for details -h, --help Print help information
Run `cargo help new` for more detailed information.
#![allow(overflowing_literals)] fn main() { assert_eq!(1000 as u16, 1000);
assert_eq!(1000 as u8, 232);
// 事实上,之前说的规则对于正整数而言,就是如下的取模 println!("1000 mod 256 is : {}", 1000 % 256);
assert_eq!(-1_i8 as u8, 255); // 从 Rust 1.45 开始,当浮点数超出目标整数的范围时,转化会直接取正整数取值范围的最大或最小值 assert_eq!(300.1_f32 as u8, 255); assert_eq!(-100.1_f32 as u8, 0);
// 上面的浮点数转换有一点性能损耗,如果对于某段代码有极致的性能要求, // 可以考虑下面的方法,但是这些方法的结果可能会溢出并且返回一些无意义的值 // 总之,请小心使用 unsafe { // 300.0 is 44 println!("300.0 is {}", 300.0_f32.to_int_unchecked::<u8>()); // -100.0 as u8 is 156 println!("-100.0 as u8 is {}", (-100.0_f32).to_int_unchecked::<u8>()); // nan as u8 is 0 println!("nan as u8 is {}", f32::NAN.to_int_unchecked::<u8>()); } }
fn main() { let mut values: [i32; 2] = [1, 2]; let p1: *mut i32 = values.as_mut_ptr(); let first_address = p1 as usize; let second_address = first_address + 4; // 4 == std::mem::size_of::<i32>() let p2 = second_address as *mut i32; unsafe { *p2 += 1; } assert_eq!(values[1], 3);
println!("Success!") } fn main() { let arr :[u64; 13] = [0; 13]; assert_eq!(std::mem::size_of_val(&arr), 8 * 13); let a: *const [u64] = &arr; let b = a as *const [u8]; unsafe { assert_eq!(std::mem::size_of_val(&*b), 13) } }
From/Into
From 特征允许让一个类型定义如何基于另一个类型来创建自己,因此它提供了一个很方便的类型转换的方式。
From 和 Into 是配对的,我们只要实现了前者,那后者就会自动被实现:只要实现了 impl From for U, 就可以使用以下两个方法: let u: U = U::from(T) 和 let u:U = T.into(),前者由 From 特征提供,而后者由自动实现的 Into 特征提供。
需要注意的是,当使用 into 方法时,需要进行显式地类型标注,因为编译器很可能无法帮我们推导出所需的类型。
// Into 特征拥有一个方法`into`, // 因此 TryInto 有一个方法是 ? let n: u8 = match n.try_into() { Ok(n) => n, Err(e) => { println!("there is an error when converting: {:?}, but we catch it", e.to_string()); 0 } };
impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "The point is ({}, {})", self.x, self.y) } }
fn main() { let origin = Point { x: 0, y: 0 }; assert_eq!(origin.to_string(), "The point is (0, 0)"); assert_eq!(format!("{}", origin), "The point is (0, 0)");
// To use `from_str` method, you needs to introduce this trait into the current scope. use std::str::FromStr; fn main() { let parsed: i32 = "5".parse().unwrap(); let turbo_parsed = "10".parse::<i32>().unwrap(); let from_str = i32::from_str("20").unwrap(); let sum = parsed + turbo_parsed + from_str; assert_eq!(sum, 35);
fn main() { /*Turning raw bytes(&[u8]) to u32, f64, etc.: */ let raw_bytes = [0x78, 0x56, 0x34, 0x12];
let num = unsafe { std::mem::transmute::<[u8; 4], u32>(raw_bytes) };
// use `u32::from_ne_bytes` instead let num = u32::from_ne_bytes(raw_bytes); // or use `u32::from_le_bytes` or `u32::from_be_bytes` to specify the endianness let num = u32::from_le_bytes(raw_bytes); assert_eq!(num, 0x12345678); let num = u32::from_be_bytes(raw_bytes); assert_eq!(num, 0x78563412);
/*Turning a pointer into a usize: */ let ptr = &0; let ptr_num_transmute = unsafe { std::mem::transmute::<&i32, usize>(ptr) };
// Use an `as` cast instead let ptr_num_cast = ptr as *const i32 as usize;
/*Turning an &mut T into an &mut U: */ let ptr = &mut 0; let val_transmuted = unsafe { std::mem::transmute::<&mut i32, &mut u32>(ptr) };
// Now, put together `as` and reborrowing - note the chaining of `as` // `as` is not transitive let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };
/*Turning an &str into a &[u8]: */ // this is not a good way to do this. let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") }; assert_eq!(slice, &[82, 117, 115, 116]);
// You could use `str::as_bytes` let slice = "Rust".as_bytes(); assert_eq!(slice, &[82, 117, 115, 116]);
// Or, just use a byte string, if you have control over the string // literal assert_eq!(b"Rust", &[82, 117, 115, 116]); }
函数
Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词
fn get_option(tp: u8) -> Option<i32> { match tp { 1 => { // TODO } _ => { // TODO } };
never_return_fn() }
// IMPLEMENT this function // DON'T change any code else fn never_return_fn() -> ! { unimplemented!() } // IMPLEMENT this function in THREE ways fn never_return_fn() -> ! { panic!() }
// IMPLEMENT this function in THREE ways fn never_return_fn() -> ! { todo!(); } // IMPLEMENT this function in THREE ways fn never_return_fn() -> ! { loop { std::thread::sleep(std::time::Duration::from_secs(1)) } }
The difference between unimplemented! and [todo] is that while todo! conveys an intent of implementing the functionality later and the message is “not yet implemented”, unimplemented! makes no such claims. Its message is “not implemented”. Also some IDEs will mark todo!s.
fn get_option(tp: u8) -> Option<i32> { match tp { 1 => { // TODO } _ => { // TODO } };
never_return_fn() }
// IMPLEMENT this function // DON'T change any code else fn never_return_fn() -> ! { loop { std::thread::sleep(std::time::Duration::from_secs(1)) } }
使用unimplemented!()和todo!();会报以下错误
thread ‘main’ panicked at ‘not implemented’, src\main.rs:24:5
1 2 3 4 5 6 7 8
let _v = match b { true => 1, // 发散函数也可以用于 `match` 表达式,用于替代任何类型的值 false => { println!("Success!"); panic!("we have no value for `false`, but we can panic") } };
控制流
if表达式
1 2 3 4 5 6 7 8 9
fn main() { let number = 3;
if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
if 表达式中与条件关联的代码块有时被叫做 *arms
if/else 可以用作表达式来进行赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
fn main() { let n = 5;
let big_n = if n < 10 && n > -10 { println!(" 数字太小,先增加 10 倍再说");
10 * n } else { println!("数字太大,我们得让它减半");
n / 2 };
println!("{} -> {}", n, big_n); }
注意
1.代码中的条件 必须 是 bool 值。如果条件不是 bool 值,我们将得到一个错误。Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 if 的条件。
2.如果使用了多于1个else if最好使用match对代码进行重构
在let语句中使用if
因为 if 是一个表达式,我们可以在 let 语句的右侧使用它
1 2 3 4 5 6
fn main() { let condition = true; let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}"); }
if 的每个分支的可能的返回值都必须是相同类型
注意
if 代码块中的表达式返回一个整数,而 else 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道变量的类型
fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }
所有权,引用与借用
栈(Stack)与堆(Heap)
栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)。
增加数据叫做 进栈(pushing onto the stack),而移出数据叫做 出栈(popping off the stack)。栈中的所有数据都必须占用已知且固定的大小。
在编译时大小未知或大小可能变化的数据,要改为存储在堆上。 堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。(将数据推入栈中并不被认为是分配)。因为指向放入堆中数据的指针是已知的并且大小是固定的,你可以将该指针存储在栈上,不过当需要实际数据时,必须访问指针。
fn main() { let s = give_ownership(); println!("{}", s); }
// 只能修改下面的代码! fn give_ownership() -> String { let s = String::from("hello, world"); // convert String to Vec // 将 String 转换成 Vec 类型 let _s = s.into_bytes();//into_bytes会转移所有权 s }
方法
1 2 3 4 5 6 7 8 9 10 11 12
fn main() { let s = give_ownership(); println!("{}", s); }
// Only modify the code below! fn give_ownership() -> String { let s = String::from("hello, world"); // convert String to Vec let _s = s.as_bytes();//as_bytes不会转移所有权 s }
或
1 2 3 4 5 6 7 8 9 10
fn main() { let s = give_ownership(); println!("{}", s); }
// Only modify the code below! fn give_ownership() -> String { let s = String::from("hello, world"); s }
当所有权转移时,可变性也可以随之改变。
1 2 3 4 5 6 7
fn main() { let s = String::from("hello, "); let mut s1 = s;
s1.push_str("world") }
变量与作用域
作用域是一个项(item)在程序中有效的范围。假设有这样一个变量:
let s = “hello”;
变量 s 绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前 作用域 结束时都是有效的。示例 4-1 中的注释标明了变量 s 在何处是有效的。
1 2 3 4 5
{ // s 在这里无效, 它尚未声明 let s = "hello"; // 从此处起,s 是有效的
println!("{}, world!", s1); warning: unused variable: `s2` --> src\main.rs:3:9 | 3 | let s2 = s1; | ^^ help: if this is intentional, prefix it with an underscore: `_s2` | = note: `#[warn(unused_variables)]` on by default
error[E0382]: borrow of moved value: `s1` --> src\main.rs:5:28 | 2 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0382`. warning: `loop_test` (bin "loop_test") generated 1 warning error: could not compile `loop_test` due to previous error; 1 warning emitted
fn main() { let reference_to_nothing = dangle(); }
fn dangle() -> &String { let s = String::from("hello");//s在函数结束后就drop了 &s//返回引用,但是函数结束后该地址就被释放掉了 } Compiling loop_test v0.1.0 (C:\Users\cauchy\Desktop\rust\loop_test) error[E0106]: missing lifetime specifier --> src\main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 5 | fn dangle() -> &'static String { | +++++++
For more information about this error, try `rustc --explain E0106`. error: could not compile `loop_test` due to previous error
ref
ref 与 & 类似,可以用来获取一个值的引用,但是它们的用法有所不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fn main() { let c = '中';
let r1 = &c; // 填写空白处,但是不要修改其它行的代码 let ref r2 = c;
fn main() { let my_string = String::from("hello world");
// `first_word` 适用于 `String`(的 slice),整体或全部 let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); // `first_word` 也适用于 `String` 的引用, // 这等价于整个 `String` 的 slice let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` 适用于字符串字面值,整体或全部 let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]);
// 因为字符串字面值已经 **是** 字符串 slice 了, // 这也是适用的,无需 slice 语法! let word = first_word(my_string_literal); }
&String可以被隐式地转换为&str类型
1 2 3 4 5 6 7 8 9 10 11 12 13
fn main() { let mut s = String::from("hello world");
fn main() { let s = String::from("hello, 世界"); let slice1 = &s[0..1]; //提示: `h` 在 UTF-8 编码中只占用 1 个字节 assert_eq!(slice1, "h");
let slice2 = &s[7..10];// 提示: `中` 在 UTF-8 编码中占用 3 个字节 assert_eq!(slice2, "世"); // 迭代 s 中的所有字符 for (i, c) in s.chars().enumerate() { if i == 7 { assert_eq!(c, '世') } }
fn main() { // 你可以使用转义的方式来输出想要的字符,这里我们使用十六进制的值,例如 \x73 会被转义成小写字母 's' // 填空以输出 "I'm writing Rust" let byte_escape = "I'm writing Ru\x73__!"; println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// 也可以使用 Unicode 形式的转义字符 let unicode_codepoint = "\u{211D}"; let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
println!("Unicode character {} (U+211D) is called {}", unicode_codepoint, character_name );
// 还能使用 \ 来连接多行字符串 let long_string = "String literals can span multiple lines. The linebreak and indentation here \ can be escaped too!"; println!("{}", long_string); }
fn main() { let raw_str = r"Escapes don't work here: \x3F \u{211D}"; println!("{}", raw_str);
// 如果字符串包含双引号,可以在开头和结尾加 # let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes);
// 如果还是有歧义,可以继续增加,没有限制 let longer_delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", longer_delimiter); } fn main() { let raw_str = "Escapes don't work here: \x3F \u{211D}"; assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
// If you need quotes in a raw string, add a pair of #s let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes);
// If you need "# in your string, just use more #s in the delimiter. // You can use up to 65535 #s. let delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", delimiter);
// Fill the blank let long_delimiter = r###"Hello, "##""###; assert_eq!(long_delimiter, "Hello, \"##\"") }
这里r#”标记一个原始字符串的开始,”#标记一个字符串的结束,如果还是有歧义可以继续加#
创建一个新的String
String::new()
let mut s = String::new();
使用to_string()
1 2 3 4 5 6
let data = "initial contents";
let s = data.to_string();
// 该方法也可直接用于字符串字面值: let s = "initial contents".to_string();
string::from()
let s = String::from(“initial contents”);
更新String
push_str()
1 2
let mut s = String::from("foo"); s.push_str("bar");
push_str()方法不会获得参数的所有权
1 2 3 4 5 6 7 8
let mut s1 = String::from("foo"); let s2 = "bar"; s1.push_str(s2); println!("s2 is {}", s2); let mut s1 = String::from("foo"); let s2 = "bar"; s1.push_str(&s2); println!("s2 is {}", s2);
push()
push 方法被定义为获取一个单独的字符作为参数,并附加到 String 中
1 2
let mut s = String::from("lo"); s.push('l');
+连接字符串
只能将String跟&str类型进行拼接,并且String的所有权在此过程中会被 move
1 2 3
let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
// 字节数组也可以使用转义 let escaped = b"\x52\x75\x73\x74 as bytes"; // ...但是不支持 unicode 转义 // let escaped = b"\u{211D} is not allowed"; println!("Some escaped bytes: {:?}", escaped);
// raw string let raw_bytestring = br"\u{211D} is not escaped here"; println!("{:?}", raw_bytestring);
// 将字节数组转成 `str` 类型可能会失败 if let Ok(my_str) = str::from_utf8(raw_bytestring) { println!("And the same as text: '{}'", my_str); }
let _quotes = br#"You can also use "fancier" formatting, \ like with normal raw strings"#;
// 字节数组可以不是 UTF-8 格式 let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS
// 修复错误,尽可能少的去修改代码 // 不要移除任何代码行! use std::collections::HashMap; fn main() { let v1 = 10; let mut m1 = HashMap::new(); m1.insert(v1, v1); println!("v1 is still usable after inserting to hashmap : {}", v1);
let v2 = "hello".to_string(); let mut m2 = HashMap::new(); // 所有权在这里发生了转移 m2.insert(v2, v1);
use std::hash::BuildHasherDefault; use std::collections::HashMap; // 引入第三方的哈希函数 use twox_hash::XxHash64;
let mut hash: HashMap<_, _, BuildHasherDefault<XxHash64>> = Default::default(); hash.insert(42, "the answer"); assert_eq!(hash.get(&42), Some(&"the answer"));
错误处理
大部分情况下,在编译时提示错误,并处理
错误的分类
可恢复:
例如文件未找到,可再次尝试
不可恢复
bug,例如访问索引超出范围
Rust没有类似异常的机制
可恢复的错误:Result
不可恢复:panic!宏
不可恢复的错误与panic!
当panic!宏执行
程序打印一个错误信息
展开(unwind),清理调用栈(Stack)
退出程序
为应对panic,展开或中止(abort)调用栈
默认情况下,当panic发生
程序展开调用栈(工作量大)
Rust沿着调用栈往回走
清理每个遇到的函数中的数据
或立即中止调用栈
不进行清理,直接停止程序
内存需要由OS进行清理
想让二进制文件更小,把设置从“展开”改为“中止”
1 2
[profile.release] panic = 'abort'
自己写的代码中内panic
1 2 3
fn main(){ panic!("crash and burn"); }
所以依赖的代码中:外部panic
1 2 3 4
fn main(){ let vector=vec![1,2,3]; vector[100]; }
输出
1 2 3 4 5 6
Compiling demo v0.1.0 (C:\Users\cauchy\Desktop\rust\demo) Finished dev [unoptimized + debuginfo] target(s) in 0.18s Running `target\debug\demo.exe` thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src\main.rs:3:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\demo.exe` (exit code: 101)
通过调用panic!的函数的回溯信息来定位引起问题的代码
使用以下命令运行代码(Windows cmd)
set RUST_BACKTRACE=1 && cargo run
Windows Poweshell
$Env:RUST_BACKTRACE=1 -and (cargo run)
linux下
export RUST_BACKTRACE=1 && cargo run
更详细的信息
RUST_BACKTRACE=full
为了获取带有调试信息的回溯,必须启用调试符号(不带—release)
Result枚举与可恢复的错误
1 2 3 4
enum Result<T, E> { Ok(T), Err(E), }
T 代表成功时返回的 Ok 成员中的数据的类型,
而 E 代表失败时返回的 Err 成员中的错误的类型
Result及其变体也是由prelude带入作用域的
打开文件
1 2 3 4 5 6 7 8 9 10
use std::fs::File;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => panic!("Problem opening the file: {:?}", error), }; }
匹配不同的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use std::fs::File; use std::io::ErrorKind;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => { panic!("Problem opening the file: {:?}", other_error) } }, }; }
闭包(closure)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use std::fs::File; use std::io::ErrorKind;
fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
unwrap
match表达式的一个快捷方法
1 2 3 4 5
use std::fs::File;
fn main() { let f = File::open("hello.txt").unwrap(); }
相当于
1 2 3 4 5 6 7 8 9 10
use std::fs::File;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => panic!("Problem opening the file: {:?}", error), }; }
如果Result结果是Ok,返回Ok里面的值
如果Result结果是Err,调用panic!宏
expect
可自定义错误信息的unwrap
1 2 3 4 5
use std::fs::File;
fn main() { let f = File::open("hello.txt").expect("无法打开文件"); }
use std::fs::File; use std::io; use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
? 被定义为与自定义传播错误的示例中定义的处理 Result 值的 match 表达式有着完全相同的工作方式。
fn read_file1() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
fn read_file2() -> Result<String, io::Error> { let mut s = String::new();
// With the return type rewritten, we use pattern matching without `unwrap()`. // But it's so Verbose.. fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> { match n1_str.parse::<i32>() { Ok(n1) => { match n2_str.parse::<i32>() { Ok(n2) => { Ok(n1 * n2) }, Err(e) => Err(e), } }, Err(e) => Err(e), } }
// Rewriting `multiply` to make it succinct // You MUST USING `and_then` and `map` here fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> { // IMPLEMENT... n1_str.parse::<i32>().and_then(|n1| { n2_str.parse::<i32>().map(|n2| n1 * n2) }) }
fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } }
fn main() { // This still presents a reasonable answer. let twenty = multiply1("10", "2"); print(twenty);
// The following now provides a much more helpful error message. let tt = multiply("t", "2"); print(tt);
println!("Success!") }
何时panic!
总体原则
在定义一个可能失败的函数时,优先考虑返回Result
否则就panic!
场景
演示某些概念:unwrap
原型代码:unwrap,expect
测试:unwrap,expect
有时你比编译器掌握更多的信息
你可以确定Result就是Ok:unwrap
1 2
use std::net::IpAddr; let home: IpAddr = "127.0.0.1".parse().unwrap();
调用你的代码,传入无意义的参数值:panic!
调用外部不可控代码,返回非法状态,你无法修复:panic!
如果失败是可预期的:Result
当你的代码对值进行操作,首先应该验证这些值:panic!
创建自定义类型进行有效性验证
一种实现方式是将猜测解析成 i32 而不仅仅是 u32,来默许输入负数,接着检查数字是否在范围内:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
loop { // --snip--
let guess: i32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };
if guess < 1 || guess > 100 { println!("The secret number will be between 1 and 100."); continue; }
fn largest_i32(list: &[i32]) -> i32 { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest }
fn largest_char(list: &[char]) -> char { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest }
fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest_i32(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest_char(&char_list); println!("The largest char is {}", result); }
使用泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
fn largest<T>(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest }
fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result); }
运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Compiling demo v0.1.0 (C:\Users\cauchy\Desktop\rust\demo) error[E0369]: binary operation `>` cannot be applied to type `T` --> src\main.rs:5:17 | 5 | if item > largest { | ---- ^ ------- T | | | T | help: consider restricting type parameter `T` | 1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T { | ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`. error: could not compile `demo` due to previous error
简单来说,这个错误表明 largest 的函数体不能适用于 T 的所有可能的类型
因为在函数体需要比较 T 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 std::cmp::PartialOrd trait 可以实现类型的比较功能
结构体中定义泛型
文件名: src/main.rs
1 2 3 4 5 6 7 8 9
struct Point<T> { x: T, y: T, }
fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; }
// fix the errors in main fn main() { check_size([0u8; 767]); check_size([0i32; 191]); check_size(["hello你好"; 47]); // &str is a string reference, containing a pointer and string length in it, so it takes two word long, in x86-64, 1 word = 8 bytes check_size([(); 31].map(|_| "hello你好".to_string())); // String is a smart pointer struct, it has three fields: pointer, length and capacity, each takes 8 bytes check_size(['中'; 191]); // A char takes 4 bytes in Rust }
fn returns_summarizable(switch: bool) -> impl Summary { if switch { NewsArticle { headline: String::from( "Penguins win the Stanley Cup Championship!", ), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), } } else { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } } }
fn main() { let random_number = 0.234; let animal = random_animal(random_number); println!("You've randomly chosen an animal, and it says {}", animal.noise()); }
let result = largest(&number_list); println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list); println!("The largest char is {}", result); }
如果并不希望限制 largest 函数只能用于实现了 Copy trait 的类型,我们可以在 T 的 trait bounds 中指定 Clone 而不是 Copy。并克隆 slice 的每一个值使得 largest 函数拥有其所有权。使用 clone 函数意味着对于类似 String 这样拥有堆上数据的类型,会潜在的分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fn largest<T: PartialOrd + Clone>(list: &[T]) -> T { let mut largest = list[0].clone();
for item in list { if item > &largest { largest = item.clone(); } }
largest }
fn main() { let str_list = vec![String::from("hello"),String::from("world")]; let result = largest(&str_list) println!("The largest word is {}", result); }
或者largest直接返回一个引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T { let mut largest = &list[0];
for item in list { if item > &largest { largest = item; } }
largest }
fn main() { let str_list = vec![String::from("hello"),String::from("world")]; let result = largest(&str_list) println!("The largest word is {}", result); }
impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }
{ let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!("r: {}", r); // | }
这里将 r 的生命周期标记为 ‘a 并将 x 的生命周期标记为 ‘b。如你所见,内部的 ‘b 块要比外部的生命周期 ‘a 小得多。在编译时,Rust 比较这两个生命周期的大小,并发现 r 拥有生命周期 ‘a,不过它引用了一个拥有生命周期 ‘b 的对象。程序被拒绝编译,因为生命周期 ‘b 比生命周期 ‘a 要小:被引用的对象比它的引用者存在的时间更短。
函数中的泛型生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz";
let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); }
执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
error[E0106]: missing lifetime specifier --> src\main.rs:1:33 | 1 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` help: consider introducing a named lifetime parameter | 1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++
For more information about this error, try `rustc --explain E0106`. error: could not compile `demo` due to previous error
标识生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz";
let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); }
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
它的实际含义是 longest 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致。
记住通过在函数签名中指定生命周期参数时,我们并没有改变任何传入值或返回值的生命周期,而是指出任何不满足这个约束条件的值都将被借用检查器拒绝。注意 longest 函数并不需要知道 x 和 y 具体会存在多久,而只需要知道有某个可以被 ‘a 替代的作用域将会满足这个签名。
当具体的引用被传递给 longest 时,被 ‘a 所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分。换一种说法就是泛型生命周期 ‘a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个。因为我们用相同的生命周期参数 ‘a 标注了返回的引用值,所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效。
使用
1 2 3 4 5 6 7 8 9
fn main() { let string1 = String::from("long string is long");
{ let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } }
在这个例子中,string1 直到外部作用域结束都是有效的,string2 则在内部作用域中是有效的,而 result 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 The longest string is long string is long。
修改
1 2 3 4 5 6 7 8 9
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); }
程序运行出错
错误表明为了保证 println! 中的 result 是有效的,string2 需要直到外部作用域结束都是有效的。Rust 知道这些是因为(longest)函数的参数和返回值都使用了相同的生命周期参数 ‘a。
深入理解声明周期
指定生命周期参数的正确方式依赖函数实现的具体功能
如果将 longest 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice,就不需要为参数 y 指定一个生命周期。如下代码将能够编译:
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence, }; }
fn main(){ let novel = String::from("Call me Ishmael.Some year ago..."); let first_sentence = novel.split('.').next().expect("Could not found a '.'"); let i = ImportantExcerpt{ part: first_sentence, }; }
#[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!(result.contains("Carol")); } }
自定义错误信息
1 2 3 4 5 6 7 8 9
#[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); }
// --snip-- impl Guess { pub fn new(value: i32) -> Guess { if value < 1 { panic!( "Guess value must be greater than or equal to 1, got {}.", value ); } else if value > 100 { panic!( "Guess value must be less than or equal to 100, got {}.", value ); }
Guess { value } } }
#[cfg(test)] mod tests { use super::*;
#[test] #[should_panic(expected = "Guess value must be less than or equal to 100")] fn greater_than_100() { Guess::new(200); } }
在测试中使用Result
无需panic,可以使用Result作为返回类型编写测试
返回Ok:测试通过
返回Err:测试失败
1 2 3 4 5 6 7 8 9 10 11
#[cfg(test)] mod tests { #[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) } else { Err(String::from("two plus two does not equal four")) } } }
pub fn run(config: Config) -> Result<(), Box<dyn Error>> { //读取文件 let contents = fs::read_to_string(&config.filename)?; // println!("With text:\n{}", contents); let results = if config.case_sensitive { search(&config.query, &contents) } else { search_case_insensitive(&config.query, &contents) }; for line in results { println!("{}", line); } Ok(()) } pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool, } impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); // println!("Search for {}", query); // println!("In file {}", filename); let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive, }) } }
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut result = Vec::new(); for line in contents.lines() { if line.contains(query) { result.push(line); } }
result } pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut result = Vec::new(); let query = query.to_lowercase(); for line in contents.lines() { if line.to_lowercase().contains(&query) { result.push(line); } }
result }
#[cfg(test)] mod tests { use super::*; #[test] fn case_sensitive() { let query = "duct"; let contents = "\ Rust: safe,fast,productive. Pick three."; assert_eq!(vec!["safe,fast,productive."], search(query, contents)); }
In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
//! # My Crate //! //! `my_crate` is a collection of utilities to make performing certain //! calculations more convenient.
/// Adds one to the number given. // --snip--
使用pub use导出方便使用的公共API
你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用 use my_crate::some_module::another_module::UsefulType; 而不是 use my_crate::UsefulType; 来使用类型。
//! # Art //! //! A library for modeling artistic concepts.
pub mod kinds { /// The primary colors according to the RYB color model. pub enum PrimaryColor { Red, Yellow, Blue, }
/// The secondary colors according to the RYB color model. pub enum SecondaryColor { Orange, Green, Purple, } }
pub mod utils { use crate::kinds::*;
/// Combines two primary colors in equal amounts to create /// a secondary color. pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { // --snip-- SecondaryColor::Green } }
文件名: src/main.rs
1 2 3 4 5 6 7 8
use art::kinds::PrimaryColor; use art::utils::mix;
fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow); }
为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例 中的 art crate 并增加 pub use 语句来重导出项到顶层结构,如示例 14-5 所示:
文件名: src/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//! # Art //! //! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor; pub use self::kinds::SecondaryColor; pub use self::utils::mix;
pub mod kinds { // --snip-- }
pub mod utils { // --snip-- }
使用
文件名: src/main.rs
1 2 3 4 5 6
use art::mix; use art::PrimaryColor;
fn main() { // --snip-- }
###
发布Crate
有了唯一的名称、版本号、由 cargo new 新建项目时增加的作者信息、描述和所选择的 license,已经准备好发布的项目的 Cargo.toml 文件可能看起来像这样:
文件名: Cargo.toml
1 2 3 4 5 6 7 8 9
[package] name = "guessing_game" version = "0.1.0" edition = "2021" description = "A fun game where you guess what number the computer has chosen." license = "MIT OR Apache-2.0" author = "cyberboy"
fn main() { let m = MyBox::new(String::from("Rust")); hello(&(*m)[..]); }
解引用与可变性
可使用DerefMut trait重载可变引用的*运算符
在类型和trait在下列三种情况发生时,Rust会执行deref coercion:
当T: Deref,允许&T 转换为&U
当T: Deref,允许&mut T转换为&mut U
当T: Deref,允许&mut T转换为&U
Drop Trait
实现Drop Trait 可以让我们自定义当值将要离开作用域时发生的动作
例如:文件,网络资源释放等
任何类型都可以实现Drop trait
Drop Trait只要求你实现drop 方法
参数: 对self的可变引用
Drop trait 在预导入模块里
文件名: src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
struct CustomSmartPointer { data: String, }
impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } }
fn main() { let c = CustomSmartPointer { data: String::from("my stuff"), }; let d = CustomSmartPointer { data: String::from("other stuff"), }; println!("CustomSmartPointers created."); }
当运行这个程序,会出现如下输出:
1 2 3 4 5 6 7
$ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished dev [unoptimized + debuginfo] target(s) in 0.60s Running `target/debug/drop-example` CustomSmartPointers created. Dropping CustomSmartPointer with data `other stuff`! Dropping CustomSmartPointer with data `my stuff`!
使用std::mem::drop来提前drop值
很难直接禁用自动的drop功能,也没必要
Drop trait的目的就是进行自动的释放处理逻辑
Rust 不允许手动调用Drop trait的drop方法
但可以调用标准库的std::mem::drop函数(prelude),来提前drop值
文件名: src/main.rs
1 2 3 4 5 6 7 8
fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }
运行这段代码会打印出如下:
1 2 3 4 5 6 7
$ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished dev [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/drop-example` CustomSmartPointer created. Dropping CustomSmartPointer with data `some data`! CustomSmartPointer dropped before the end of main.
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 drop 只会在值不再被使用时被调用一次。
fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b = Cons(3, Box::new(a)); let c = Cons(4, Box::new(a)); }
我们修改 List 的定义为使用 Rc 代替 Box,如列表 所示。现在每一个 Cons 变量都包含一个值和一个指向 List 的 Rc。当创建 b 时,不同于获取 a 的所有权,这里会克隆 a 所包含的 Rc,这会将引用计数从 1 增加到 2 并允许 a 和 b 共享 Rc 中数据的所有权。创建 c 时也会克隆 a,这会将引用计数从 2 增加为 3。每次调用 Rc::clone,Rc 中数据的引用计数都会增加,直到有零个引用之前其数据都不会被清理。
文件名: src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13
enum List { Cons(i32, Rc<List>), Nil, }
use crate::List::{Cons, Nil}; use std::rc::Rc;
fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }
fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("count after creating a = {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); }
这段代码会打印出:
1 2 3 4 5 6 7 8
$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished dev [unoptimized + debuginfo] target(s) in 0.45s Running `target/debug/cons-list` count after creating a = 1 count after creating b = 2 count after creating c = 3 count after c goes out of scope = 2
我们能够看到 a 中 Rc 的初始引用计数为1,接着每次调用 clone,计数会增加1。当 c 离开作用域时,计数减1。不必像调用 Rc::clone 增加引用计数那样调用一个函数来减少计数;Drop trait 的实现当 Rc 值离开作用域时自动减少引用计数。
从这个例子我们所不能看到的是,在 main 的结尾当 b 然后是 a 离开作用域时,此处计数会是 0,同时 Rc 被完全清理。使用 Rc 允许一个值有多个所有者,引用计数则确保只要任何所有者依然存在其值也保持有效。
$ cargo run Compiling borrowing v0.1.0 (file:///projects/borrowing) error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable --> src/main.rs:3:13
如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } } #[cfg(test)] mod tests { use super::*;
$ cargo test Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/lib.rs:58:13 | 2 | fn send(&self, msg: &str); | ----- help: consider changing that to be a mutable reference: `&mut self` ... 58 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`. error: could not compile `limit-tracker` due to previous error warning: build failed, waiting for other jobs to finish... error: build failed
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a)); println!("b initial rc count = {}", Rc::strong_count(&b)); println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); }
println!("b rc count after changing a = {}", Rc::strong_count(&b)); println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle; // it will overflow the stack // println!("a next item = {:?}", a.tail()); }
如果保持最后的 println! 行注释并运行代码,会得到如下输出:
1 2 3 4 5 6 7 8 9 10 11
$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished dev [unoptimized + debuginfo] target(s) in 0.53s Running `target/debug/cons-list` a initial rc count = 1 a next item = Some(RefCell { value: Nil }) a rc count after b creation = 2 b initial rc count = 1 b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) }) b rc count after changing a = 2 a rc count after changing a = 2
如果取消最后 println! 的注释并运行程序,Rust 会尝试打印出 a 指向 b 指向 a 这样的循环直到栈溢出。
fn main() { thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } }
当主线程结束时,新线程也会结束,而不管其是否执行完毕。
通过join Handle来等待所有线程的完成
thread::spawn 的返回值类型是 JoinHandle。
JoinHandle 是一个拥有所有权的值
当对其调用 join 方法时,会阻止当前运行线程的执行,直到handle所表示的这些线程的终结。
文件名: src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use std::thread; use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); }
$ cargo run Compiling threads v0.1.0 (file:///projects/threads) error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:6:32 | 6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v` 7 | println!("Here's a vector: {:?}", v); | - `v` is borrowed here | note: function requires argument type to outlive `'static` --> src/main.rs:6:18 | 6 | let handle = thread::spawn(|| { | __________________^ 7 | | println!("Here's a vector: {:?}", v); 8 | | }); | |______^ help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword | 6 | let handle = thread::spawn(move || { | ++++
For more information about this error, try `rustc --explain E0373`. error: could not compile `threads` due to previous error
Rust 会 推断 如何捕获 v,因为 println! 只需要 v 的引用,闭包尝试借用 v。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓 v 的引用是否一直有效。
示例 16-4 展示了一个 v 的引用很有可能不再有效的场景:
文件名: src/main.rs
1 2 3 4 5 6 7 8 9 10 11 12 13
use std::thread;
fn main() { let v = vec![1, 2, 3];
let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); });
let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); });
handle.join().unwrap(); }
示例 16-5: 使用 move 关键字强制获取它使用的值的所有权
使用消息传递来跨线程传递数据
一个日益流行的确保安全并发的方式是 消息传递(message passing),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中]的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存。”(“Do not communicate by sharing memory; instead, share memory by communicating.”)
$ cargo run Compiling message-passing v0.1.0 (file:///projects/message-passing) error[E0382]: borrow of moved value: `val` --> src/main.rs:10:31 | 8 | let val = String::from("hi"); | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait 9 | tx.send(val).unwrap(); | --- value moved here 10 | println!("val is {}", val); | ^^^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`. error: could not compile `message-passing` due to previous error
$ cargo run Compiling shared-state v0.1.0 (file:///projects/shared-state) error[E0382]: use of moved value: `counter` --> src/main.rs:9:36 | 5 | let counter = Mutex::new(0); | ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait ... 9 | let handle = thread::spawn(move || { | ^^^^^^^ value moved into closure here, in previous iteration of loop 10 | let mut num = counter.lock().unwrap(); | ------- use occurs due to use in closure
For more information about this error, try `rustc --explain E0382`. error: could not compile `shared-state` due to previous error
$ cargo run Compiling shared-state v0.1.0 (file:///projects/shared-state) error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely --> src/main.rs:11:22 | 11 | let handle = thread::spawn(move || { | ______________________^^^^^^^^^^^^^_- | | | | | `Rc<Mutex<i32>>` cannot be sent between threads safely 12 | | let mut num = counter.lock().unwrap(); 13 | | 14 | | *num += 1; 15 | | }); | |_________- within this `[closure@src/main.rs:11:36: 15:10]` | = help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>` = note: required because it appears within the type `[closure@src/main.rs:11:36: 15:10]` note: required by a bound in `spawn`
For more information about this error, try `rustc --explain E0277`. error: could not compile `shared-state` due to previous error
第一行错误表明 Rc>cannot be sent between threads safely。编译器也告诉了我们原因 the traitSendis not implemented forRc>。下一部分会讲到 Send:这是确保所使用的类型可以用于并发环境的 trait 之一。
$ cargo run Compiling gui v0.1.0 (file:///projects/gui) error[E0277]: the trait bound `String: Draw` is not satisfied --> src/main.rs:5:26 | 5 | components: vec![Box::new(String::from("Hi"))], | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String` | = note: required for the cast to the object type `dyn Draw`
For more information about this error, try `rustc --explain E0277`. error: could not compile `gui` due to previous error
$ cargo build Compiling gui v0.1.0 (file:///projects/gui) error[E0038]: the trait `Clone` cannot be made into an object --> src/lib.rs:2:29 | 2 | pub components: Vec<Box<dyn Clone>>, | ^^^^^^^^^ `Clone` cannot be made into an object | = note: the trait cannot be made into an object because it requires `Self: Sized` = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
For more information about this error, try `rustc --explain E0038`. error: could not compile `gui` due to previous error
fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8,_> = "34".parse();
if let Some(color) = favorite_color{ print!("Using your favorite color,{},as the background",color); } else if is_tuesday{ println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } }
对某些可能的值,无法进行匹配的模式:可辨驳的 例如:if let Some(x) = a_value
函数参数,let语句,for循环只接受无可辩驳的模式
if let和while let接受可辨驳和无可辩驳的模式
1 2 3 4
fn main() { let a: Option<i32> = Some(5); let Some(x) = a; }
输出
1 2 3 4 5 6 7 8
error[E0005]: refutable pattern in local binding: `None` not covered --> src\main.rs:3:9 | 3 | let Some(x) = a; | ^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
无法匹配,因为模式没有覆盖None这种情况
修改
1 2 3 4 5 6
fn main() { let a: Option<i32> = Some(5); if let Some(x) = a{ }; }
fn main(){ let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } }
匹配命名变量
命名的变量是可匹配任何值的无可辩驳模式
1 2 3 4 5 6 7 8 9 10
fn main(){ let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(y) => println!("Matched,y={:?}",y), _ => println!("Default Case,x={:?}",x), } println!("at the end: x={:?},y={:?}",x,y); }
这里match的第二个arm中y是一个新的变量,存在于该arm的作用域
匹配一个可变引用
使用模式 &mut V 去匹配一个可变引用时,你需要格外小心,因为匹配出来的 V是一个值,而不是可变引用
1 2 3 4 5 6 7 8 9
fn main() { let mut v = String::from("hello,"); let r = &mut v;
match r { // The type of value is &mut String value => value.push_str(" world!") } }
多重模式
在match表达式中,使用 | 语法(就是或的意思)可以匹配多种模式
1 2 3 4 5 6 7 8
fn main(){ let x= 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } }
使用..=来匹配某个范围的值
1 2 3 4 5 6 7 8 9 10 11 12 13
fn main(){ let x= 5; match x{ 1..=5 => println!("one through five"), _ => println!("something else"), } let x = 'c'; match x { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } }
#[derive(Debug)] struct Point{ x: i32, y: i32, } fn main(){ let p = Point{x:0,y:7}; let Point {x:a,y:b}=p; assert_eq!(0,a); assert_eq!(7,b);
//简写形式 let Point{x,y} = p; assert_eq!(0,x); assert_eq!(7,y);
match p { Point {x,y:0}=> println!("On the x axis at {}",x),//要求y必须为0 Point {x:0,y} => println!("On the y axis at {}",y),//要求x必须为0 Point {x,y} => println!("On neither axis:({},{})",x,y), } }
enum Message{ Quit, Move{x:i32,y:i32}, Write(String), ChangeColor(i32,i32,i32), } fn main(){ let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => { println!("The Quit variant has no data to destructure"); } Message::Move { x, y }=>{ println!("Move in the x direction {} and in the y direction {}",x,y); } Message::Write(text)=>{ println!("Text message:{}",text); } Message::ChangeColor(r, g, b)=>{ println!("rgb is ({},{},{})",r,g,b); } } }
enum Color{ Rgb(i32,i32,i32), Hsv(i32,i32,i32), } enum Message{ Quit, Move{x:i32,y:i32}, Write(String), ChangeColor(Color), } fn main(){ let msg = Message::ChangeColor(Color::Hsv(0, 160, 255)); match msg { Message::ChangeColor(Color::Rgb(r, g, b))=>{ println!("Change the color to red {},green {},and blue {}",r,g,b); } Message::ChangeColor(Color::Hsv(h, s, v))=>{ println!("Change the color to hue {},saturation {},and value {}",h,s,v); } _ => (), } }
fn main(){ let num = Some(4); match num { Some(x) if x < 5 => println!("less than five:{}",x), Some(x) => println!("{}",x), None => (), } } fn main(){ let x = Some(5); let y = 10; match x { Some(50) => println!("Got 50"), Some(n) if n==y => println!("Matched,n = {:?}",n),//这里if n==y不是一个模式,不会引用新的变量 _ =>println!("Default case,x ={:?}",x), } println!("at the end:x={:?},y={:?}",x,y); } fn main(){ let x= 4; let y = false; match x { 4 | 5 | 6 if y=> println!("yes"), _ => println!("no"), } }
enum Message{ Hello {id:i32}, } fn main(){ let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7, }=>{ println!("Found an id in range:{}",id_variable); } Message::Hello { id: 10..=12 }=>{ println!("Found an id in another range"); } Message::Hello { id }=>{ println!("Found some other id:{}",id); } } } struct Point { x: i32, y: i32, }
fn main() { // fill in the blank to let p match the second arm let p = Point { x: 2, y: 20 }; // x can be [0, 5], y can be 10 20 or 30
match p { Point { x, y: 0 } => println!("On the x axis at {}", x), // second arm Point { x: 0..=5, y: y@ (10 | 20 | 30) } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), } }
应用场景:
下面这段代码会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
enum Message { Hello { id: i32 }, }
fn main() { let msg = Message::Hello { id: 5 };
match msg { Message::Hello { id: 3..=7, } => println!("id 值的范围在 [3, 7] 之间: {}", id),//Error cannot find value `id` in this scope Message::Hello { id: newid@10 | 11 | 12 } => {//Error variable `newid` is not bound in all patterns pattern doesn't bind `newid` println!("id 值的范围在 [10, 12] 之间: {}", newid) } Message::Hello { id } => println!("Found some other id: {}", id), } }
修复错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
enum Message { Hello { id: i32 }, }
fn main() { let msg = Message::Hello { id: 5 };
match msg { Message::Hello { id: id @3..=7, } => println!("id 值的范围在 [3, 7] 之间: {}", id), Message::Hello { id: newid@(10 | 11 | 12) } => { println!("id 值的范围在 [10, 12] 之间: {}", newid) } Message::Hello { id } => println!("Found some other id: {}", id), } }
fn split_at_mut(slice:&mut[i32],mid:usize)->(&mut [i32],&mut[i32]){ let len = slice.len(); assert!(mid<=len); (&mut slice[..mid],&mut slice[mid..]) } fn main(){ let mut v= vec![1,2,3,4,5,6]; let r = &mut v[..]; let (a,b) = r.split_at_mut(3); assert_eq!(a,&mut [1,2,3]); assert_eq!(b,&mut [4,5,6]); }
报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14
error[E0499]: cannot borrow `*slice` as mutable more than once at a time --> src\main.rs:6:29 | 3 | fn split_at_mut(slice:&mut[i32],mid:usize)->(&mut [i32],&mut[i32]){ | - let's call the lifetime of this reference `'1` ... 6 | (&mut slice[..mid],&mut slice[mid..]) | ------------------------^^^^^-------- | | | | | | | second mutable borrow occurs here | | first mutable borrow occurs here | returning this value requires that `*slice` is borrowed for `'1`
For more information about this error, try `rustc --explain E0499`.
使用unfase代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use std::slice; fn split_at_mut(slice: &mut [i32],mid: usize)->(&mut [i32],&mut [i32]){ let len = slice.len(); let ptr = slice.as_mut_ptr(); assert!(mid<=len); unsafe{ ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len-mid) ) } } fn main(){ let mut v= vec![1,2,3,4,5,6]; let r = &mut v[..]; let (a,b) = r.split_at_mut(3); assert_eq!(a,&mut [1,2,3]); assert_eq!(b,&mut [4,5,6]); }
使用extern函数调用外部代码
extern 关键字:简化创建和使用外部函数接口(FFI)的过程
外部函数接口(FFI,Foreign Function Interface) : 它允许一种编程语言定义函数,并让其它编程语言能调用这些函数
// A trait which checks if 2 items are stored inside of container. // Also retrieves first or last value. trait Contains { // Define generic types here which methods will be able to utilize. type A; type B;
impl Contains for Container { // Specify what types `A` and `B` are. If the `input` type // is `Container(i32, i32)`, the `output` types are determined // as `i32` and `i32`. type A = i32; type B = i32;
// `&Self::A` and `&Self::B` are also valid here. fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } // Grab the first number. fn first(&self) -> i32 { self.0 }
// Grab the last number. fn last(&self) -> i32 { self.1 } }
impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking"); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self){ println!("*waving arms furiously*"); } } fn main(){ let person = Human; person.fly();//调用本身的方法 Pilot::fly(&person);//调用Pilot trait中的方法 Wizard::fly(&person);//调用Wizard trait中的方法 }
fn main() { println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");// => Alice, this is Bob. Bob, this is Alice assert_eq!(format!("{1}{0}", 1, 2), "21"); assert_eq!(format!("{1}{}{0}{}", 1, 2), "2112"); println!("Success!") }
assert_eq!(format!("{name}{}", 1, name = 2), "21"); assert_eq!(format!("{a} {c} {b}",a = "a", b = 'b', c = 3 ), "a 3 b"); // named argument must be placed after other arguments println!("{abc} {0}", 2, abc = "def");
println!("Success!") }
字符串对齐
默认情况下,通过空格来填充字符串
1 2 3 4 5 6 7 8 9 10
fn main() { // the following two are padding with 5 spaces println!("Hello {:5}!", "x"); // => "Hello x !" println!("Hello {:1$}!", "x", 5); // => "Hello x !"
assert_eq!(format!("Hello {1:0$}!", 5, "x"), "Hello x !"); assert_eq!(format!("Hello {:width$}!", "x", width = 5), "Hello x !");
println!("Success!") }
左对齐, 右对齐, 使用指定的字符填充
1 2 3 4 5 6 7 8 9 10 11 12 13
fn main() { // left align println!("Hello {:<5}!", "x"); // => Hello x ! // right align assert_eq!(format!("Hello {:>5}!", "x"), "Hello x!"); // center align assert_eq!(format!("Hello {:^5}!", "x"), "Hello x !");
// left align, pad with '&' assert_eq!(format!("Hello {:&<5}!", "x"), "Hello x&&&&!");
They get told that "str literal" is hardcoded into the compiled binary and is loaded into read-only memory at run-time so it’s immutable and valid for the entire program and that’s what makes it 'static. These concepts are further reinforced by the rules surrounding defining static variables using the static keyword.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Note: This example is purely for illustrative purposes. // Never use `static mut`. It's a footgun. There are // safe patterns for global mutable singletons in Rust but // those are outside the scope of this article.
The 'static lifetime was probably named after the default lifetime of static variables, right? So it makes sense that the 'static lifetime has to follow all the same rules, right?
Well yes, but a type with a 'static lifetime is different from a type bounded by a 'static lifetime. The latter can be dynamically allocated at run-time, can be safely and freely mutated, can be dropped, and can live for arbitrary durations.
It’s important at this point to distinguish &'static T from T: 'static.
&'static T is an immutable reference to some T that can be safely held indefinitely long, including up until the end of the program. This is only possible if T itself is immutable and does not move after the reference was created. T does not need to be created at compile-time. It’s possible to generate random dynamically allocated data at run-time and return 'static references to it at the cost of leaking memory, e.g.
1 2 3 4 5 6 7
use rand;
// generate random 'static str refs at run-time fnrand_str_generator() -> &'staticstr { letrand_string = rand::random::<u64>().to_string(); Box::leak(rand_string.into_boxed_str()) }
T: 'static is some T that can be safely held indefinitely long, including up until the end of the program. T: 'static includes all &'static T however it also includes all owned types, like String, Vec, etc. The owner of some data is guaranteed that data will never get invalidated as long as the owner holds onto it, therefore the owner can safely hold onto the data indefinitely long, including up until the end of the program. T: 'static should be read as “T is bounded by a 'static lifetime” not “T has a 'static lifetime”. A program to help illustrate these concepts:
fnmain() { letmut strings: Vec<String> = Vec::new(); for_in0..10 { if rand::random() { // all the strings are randomly generated // and dynamically allocated at run-time letstring = rand::random::<u64>().to_string(); strings.push(string); } }
// strings are owned types so they're bounded by 'static formut string in strings { // all the strings are mutable string.push_str("a mutation"); // all the strings are droppable drop_static(string); // ✅ }
// all the strings have been invalidated before the end of the program println!("I am the end of the program"); }
Key Takeaways
T: 'static should be read as “T is bounded by a 'static lifetime”
if T: 'static then T can be a borrowed type with a 'static lifetime or an owned type