背景知识
变量绑定与解构
问题一
变量只有在初始化后才能被使用
// 修复下面代码的错误并尽可能少的修改
fn main() {
let x: i32; // 未初始化,但被使用
let y: i32; // 未初始化,也未被使用
println!("x is equal to {}", x);
}
我的解答
// 修复下面代码的错误并尽可能少的修改
fn main() {
let x: i32 = 0; // 未初始化,但被使用
let _y: i32; // 未初始化,也未被使用
println!("x is equal to {}", x);
}
问题二
可以使用 mut 将变量标记为可变
// 完形填空,让代码编译
fn main() {
let __ = 1;
__ += 2;
println!("x = {}", x);
}
我的解答
// 完形填空,让代码编译
fn main() {
let mut x = 1;
x += 2;
println!("x = {}", x);
}
问题三
作用域是一个变量在程序中能够保持合法的范围
// 修复下面代码的错误并使用尽可能少的改变
fn main() {
let x: i32 = 10;
{
let y: i32 = 5;
println!("x 的值是 {}, y 的值是 {}", x, y);
}
println!("x 的值是 {}, y 的值是 {}", x, y);
}
我的解答
// 修复下面代码的错误并使用尽可能少的改变
fn main() {
let x: i32 = 10;
let y: i32 = 5;
{
println!("x 的值是 {}, y 的值是 {}", x, y);
}
println!("x 的值是 {}, y 的值是 {}", x, y);
}
最佳解答
fn main() {
let x: i32 = 10;
{
let y: i32 = 5;
println!("The value of x is {} and value of y is {}", x, y);
}
println!("The value of x is {}", x);
}
问题四
// 修复错误
fn main() {
println!("{}, world", x);
}
fn define_x() {
let x = "hello";
}
我的解答
// 修复错误
fn main() {
let x = define_x();
println!("{}, world", x);
}
fn define_x() -> &'static str {
let x = "hello";
x
}
最佳解答
fn main() {
let x = define_x();
println!("{}, world", x);
}
fn define_x() -> String {
let x = "hello".to_string();
x
}
问题五
若后面的变量声明的名称和之前的变量相同,则我们说:第一个变量被第二个同名变量遮蔽了(shadowing)
// 只允许修改 `assert_eq!` 来让 `println!` 工作(在终端输出 `42`)
fn main() {
let x: i32 = 5;
{
let x = 12;
assert_eq!(x, 5);
}
assert_eq!(x, 12);
let x = 42;
println!("{}", x); // 输出 "42".
}
我的解答
// 只允许修改 `assert_eq!` 来让 `println!` 工作(在终端输出 `42`)
fn main() {
let x: i32 = 5;
{
let x = 12;
assert_eq!(x, 12);
}
assert_eq!(x, 5);
let x = 42;
println!("{}", x); // 输出 "42".
}
问题六
删除一行代码以通过编译
fn main() {
let mut x: i32 = 1;
x = 7;
// 遮蔽且再次绑定
let x = x;
x += 3;
let y = 4;
// 遮蔽
let y = "I can also be bound to text!";
}
我的解答
fn main() {
let mut x: i32 = 1;
x = 7;
// 遮蔽且再次绑定
let x = x;
let y = 4;
// 遮蔽
let y = "I can also be bound to text!";
}
问题七
使用以下方法来修复编译器输出的 warning:
- 一种方法
- 两种方法
注意: 你可以使用两种方法解决,但是它们没有一种是移除
let x = 1
所在的代码行
fn main() {
let x = 1;
}
// compiler warning: unused variable: `x`
我的解答
fn main() {
let _x = 1;
}
// compiler warning: unused variable: `x`
#[allow(unused_variables)]
fn main() {
let x = 1;
}
// compiler warning: unused variable: `x`
问题八
我们可以将 let 跟一个模式一起使用来解构一个元组,最终将它解构为多个独立的变量
// 修复下面代码的错误并尽可能少的修改
fn main() {
let (x, y) = (1, 2);
x += 2;
assert_eq!(x, 3);
assert_eq!(y, 2);
}
我的解答
// 修复下面代码的错误并尽可能少的修改
fn main() {
let (mut x, y) = (1, 2);
x += 2;
assert_eq!(x, 3);
assert_eq!(y, 2);
}
// 修复下面代码的错误并尽可能少的修改
fn main() {
let (x, y) = (1, 2);
let x = 3;
assert_eq!(x, 3);
assert_eq!(y, 2);
}
问题九
在赋值语句的左式中使用元组、切片或结构体进行匹配赋值。
fn main() {
let (x, y);
(x, ..) = (3, 4);
[.., y] = [1, 2];
// 填空,让代码工作
assert_eq!([x, y], __);
}
我的解答
fn main() {
let (x, y);
(x, ..) = (3, 4);
[.., y] = [1, 2];
// 填空,让代码工作
assert_eq!([x, y], [3, 2]);
}
基本类型
数值类型
问题一
如果我们没有显式的给予变量一个类型,那编译器会自动帮我们推导一个类型
// 移除某个部分让代码工作
fn main() {
let x: i32 = 5;
let mut y: u32 = 5;
y = x;
let z = 10; // 这里 z 的类型是?
}
我的解答
// 移除某个部分让代码工作
fn main() {
let x: i32 = 5;
let mut y: u32 = 5;
// y = x;
let z = 10; // 这里 z 的类型是?
}
最佳解答
fn main() {
let x: i32 = 5;
let mut y = 5;
y = x;
let z = 10; // type of z : i32
}
问题二
// 填空
fn main() {
let v: u16 = 38_u8 as __;
}
我的解答
// 填空
fn main() {
let v: u16 = 38_u8 as u16;
}
问题三
// 修改 `assert_eq!` 让代码工作
fn main() {
let x = 5;
assert_eq!("u32".to_string(), type_of(&x));
}
// 以下函数可以获取传入参数的类型,并返回类型的字符串形式,例如 "i8", "u8", "i32", "u32"
fn type_of<T>(_: &T) -> String {
format!("{}", std::any::type_name::<T>())
}
我的解答
// 修改 `assert_eq!` 让代码工作
fn main() {
let x = 5;
assert_eq!("i32".to_string(), type_of(&x));
}
// 以下函数可以获取传入参数的类型,并返回类型的字符串形式,例如 "i8", "u8", "i32", "u32"
fn type_of<T>(_: &T) -> String {
format!("{}", std::any::type_name::<T>())
}
问题四
// 填空,让代码工作
fn main() {
assert_eq!(i8::MAX, __);
assert_eq!(u8::MAX, __);
}
我的解答
// 填空,让代码工作
fn main() {
assert_eq!(i8::MAX, 127);
assert_eq!(u8::MAX, 255);
}
问题五
// 解决代码中的错误和 `panic`
fn main() {
let v1 = 251_u8 + 8;
let v2 = i8::checked_add(251, 8).unwrap();
println!("{},{}",v1,v2);
}
我的解答
// 解决代码中的错误和 `panic`
fn main() {
let v1 = 251_u16 + 8;
let v2 = i16::checked_add(251, 8).unwrap();
println!("{},{}", v1, v2);
}
最佳解答
fn main() {
let v1 = 247_u8 + 8;
let v2 = i8::checked_add(119, 8).unwrap();
println!("{},{}", v1, v2);
}
问题六
// 修改 `assert!` 让代码工作
fn main() {
let v = 1_024 + 0xff + 0o77 + 0b1111_1111;
assert!(v == 1579);
}
我的解答
// 修改 `assert!` 让代码工作
fn main() {
let v = 1_024 + 0xff + 0o77 + 0b1111_1111;
assert!(v == 1597);
}
问题七
// 将 ? 替换成你的答案
fn main() {
let x = 1_000.000_1; // ?
let y: f32 = 0.12; // f32
let z = 0.01_f64; // f64
}
我的解答
// 将 ? 替换成你的答案
fn main() {
let x = 1_000.000_1; // f64
let y: f32 = 0.12; // f32
let z = 0.01_f64; // f64
}
问题八
使用两种方法来让下面代码工作
fn main() {
assert!(0.1 + 0.2 == 0.3);
}
我的解答
fn main() {
assert!((0.1_f64 + 0.2 - 0.3).abs() < 0.00001);
}
fn main() {
assert!(0.1_f32 + 0.2_f32 == 0.3_f32);
}
问题九
两个目标:
- 修改 assert! 让它工作
- 让 println! 输出: 97 - 122
fn main() {
let mut sum = 0;
for i in -3..2 {
sum += i
}
assert!(sum == -3);
for c in 'a'..='z' {
println!("{}", c);
}
}
我的解答
fn main() {
let mut sum = 0;
for i in -3..2 {
sum += i
}
assert!(sum == -5);
for c in 'a'..='z' {
println!("{}", c);
}
}
最佳解答
fn main() {
let mut sum = 0;
for i in -3..2 {
sum += i
}
assert!(sum == -5);
for c in 'a'..='z' {
println!("{}", c as u8);
}
}
问题十
// 填空
use std::ops::{Range, RangeInclusive};
fn main() {
assert_eq!((1..__), Range{ start: 1, end: 5 });
assert_eq!((1..__), RangeInclusive::new(1, 5));
}
我的解答
// 填空
use std::ops::{Range, RangeInclusive};
fn main() {
assert_eq!((1..5), Range { start: 1, end: 5 });
assert_eq!((1..=5), RangeInclusive::new(1, 5));
}
问题十一
// 填空,并解决错误
fn main() {
// 整数加法
assert!(1u32 + 2 == __);
// 整数减法
assert!(1i32 - 2 == __);
assert!(1u8 - 2 == -1);
assert!(3 * 50 == __);
assert!(9.6 / 3.2 == 3.0); // error ! 修改它让代码工作
assert!(24 % 5 == __);
// 逻辑与或非操作
assert!(true && false == __);
assert!(true || false == __);
assert!(!true == __);
// 位操作
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
}
我的解答
// 填空,并解决错误
fn main() {
// 整数加法
assert!(1u32 + 2 == 3);
// 整数减法
assert!(1i32 - 2 == -1);
assert!(1i8 - 2 == -1);
assert!(3 * 50 == 150);
assert!(9.6_f32 / 3.2_f32 == 3.0_f32); // error ! 修改它让代码工作
assert!(24 % 5 == 4);
// 逻辑与或非操作
assert!(true && false == false);
assert!(true || false == true);
assert!(!true == false);
// 位操作
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
}
字符、布尔、单元类型
问题一
// 修改 2 处 `assert_eq!` 让代码工作
use std::mem::size_of_val;
fn main() {
let c1 = 'a';
assert_eq!(size_of_val(&c1), 1);
let c2 = '中';
assert_eq!(size_of_val(&c2), 3);
println!("Success!")
}
我的解答
// 修改 2 处 `assert_eq!` 让代码工作
use std::mem::size_of_val;
fn main() {
let c1 = 'a';
assert_eq!(size_of_val(&c1), 4);
let c2 = '中';
assert_eq!(size_of_val(&c2), 4);
println!("Success!")
}
问题二
// 修改一行让代码正常打印
fn main() {
let c1 = "中";
print_char(c1);
}
fn print_char(c : char) {
println!("{}", c);
}
我的解答
// 修改一行让代码正常打印
fn main() {
let c1 = '中';
print_char(c1);
}
fn print_char(c: char) {
println!("{}", c);
}
问题三
// 使成功打印
fn main() {
let _f: bool = false;
let t = true;
if !t {
println!("Success!")
}
}
我的解答
// 使成功打印
fn main() {
let _f: bool = false;
let t = true;
if t {
println!("Success!")
}
}
问题四
fn main() {
let f = true;
let t = true && false;
assert_eq!(t, f);
println!("Success!")
}
我的解答
fn main() {
let f = true;
let t = true && false;
assert_eq!(t, !f);
println!("Success!")
}
问题五
// 让代码工作,但不要修改 `implicitly_ret_unit` !
fn main() {
let _v: () = ();
let v = (2, 3);
assert_eq!(v, implicitly_ret_unit());
println!("Success!")
}
fn implicitly_ret_unit() {
println!("I will return a ()")
}
// 不要使用下面的函数,它只用于演示!
fn explicitly_ret_unit() -> () {
println!("I will return a ()")
}
我的解答
// 让代码工作,但不要修改 `implicitly_ret_unit` !
fn main() {
let _v: () = ();
let v = ();
assert_eq!(v, implicitly_ret_unit());
println!("Success!")
}
fn implicitly_ret_unit() {
println!("I will return a ()")
}
// 不要使用下面的函数,它只用于演示!
fn explicitly_ret_unit() -> () {
println!("I will return a ()")
}
问题六
单元类型占用的内存大小是多少?
// 让代码工作:修改 `assert!` 中的 `4`
use std::mem::size_of_val;
fn main() {
let unit: () = ();
assert!(size_of_val(&unit) == 4);
println!("Success!")
}
我的解答
// 让代码工作:修改 `assert!` 中的 `4`
use std::mem::size_of_val;
fn main() {
let unit: () = ();
assert!(size_of_val(&unit) == 0);
println!("Success!")
}
语句与表达式
问题一
// 使用两种方法让代码工作起来
fn main() {
let v = {
let mut x = 1;
x += 2
};
assert_eq!(v, 3);
}
我的解答
fn main() {
let v = {
let mut x = 1;
x += 2
};
assert_eq!(v, ());
}
fn main() {
let v = {
let mut x = 1;
x += 2;
x
};
assert_eq!(v, 3);
}
问题二
fn main() {
let v = (let x = 3);
assert!(v == 3);
}
我的解答
fn main() {
let v = {
let x = 3;
x
};
assert!(v == 3);
}
问题三
fn main() {
let s = sum(1, 2);
assert_eq!(s, 3);
}
fn sum(x: i32, y: i32) -> i32 {
x + y;
}
我的解答
fn main() {
let s = sum(1, 2);
assert_eq!(s, 3);
}
fn sum(x: i32, y: i32) -> i32 {
x + y
}
函数
问题一
fn main() {
// 不要修改下面两行代码!
let (x, y) = (1, 2);
let s = sum(x, y);
assert_eq!(s, 3);
}
fn sum(x, y: i32) {
x + y;
}
我的解答
fn main() {
let (x, y) = (1, 2);
let s = sum(x, y);
assert_eq!(s, 3);
}
fn sum(x: i32, y: i32) -> i32 {
x + y
}
问题二
fn main() {
print();
}
// 使用另一个类型来替代 i32
fn print() -> i32 {
println!("hello,world");
}
我的解答
fn main() {
print();
}
fn print() -> () {
println!("hello,world");
}
问题三
// 用两种方法求解
fn main() {
never_return();
}
fn never_return() -> ! {
// 实现这个函数,不要修改函数签名!
}
我的解答
fn main() {
never_return();
}
fn never_return() -> ! {
loop {}
}
fn main() {
never_return();
}
fn never_return() -> ! {
panic!("error")
}
问题四
发散函数(Diverging function)不会返回任何值,因此它们可以用于替代需要返回任何值的地方
fn main() {
println!("Success!");
}
fn get_option(tp: u8) -> Option<i32> {
match tp {
1 => {
}
_ => {
}
};
// 这里与其返回一个 None,不如使用发散函数替代
never_return_fn()
}
// 使用三种方法实现以下发散函数
fn never_return_fn() -> ! {}
我的解答
fn never_return_fn() -> ! {
panic!()
}
fn never_return_fn() -> ! {
todo!();
}
fn never_return_fn() -> ! {
loop {
std::thread::sleep(std::time::Duration::from_secs(1))
}
}
问题五
fn main() {
// 填空
let b = __;
let _v = match b {
true => 1,
// 发散函数也可以用于 `match` 表达式,用于替代任何类型的值
false => {
println!("Success!");
panic!("we have no value for `false`, but we can panic")
}
};
println!("Exercise Failed if printing out this line!");
}
我的解答
fn main() {
let b = false;
let _v = match b {
true => 1,
// 发散函数也可以用于 `match` 表达式,用于替代任何类型的值
false => {
println!("Success!");
panic!("we have no value for `false`, but we can panic")
}
};
println!("Exercise Failed if printing out this line!");
}
所有权与借用
所有权
问题一
fn main() {
// 使用尽可能多的方法来通过编译
let x = String::from("hello, world");
let y = x;
println!("{},{}", x, y);
}
我的解答
fn main() {
let x = String::from("hello, world");
let y = x.clone();
println!("{},{}", x, y);
}
fn main() {
let x = String::from("hello, world");
let y = &x;
println!("{},{}", x, y);
}
fn main() {
let x = "hello, world";
let y = x;
println!("{},{}", x, y);
}
fn main() {
let x = String::from("hello, world");
let y = x.as_str();
println!("{},{}", x, y);
}
问题二
// 不要修改 main 中的代码
fn main() {
let s1 = String::from("hello, world");
let s2 = take_ownership(s1);
println!("{}", s2);
}
// 只能修改下面的代码!
fn take_ownership(s: String) {
println!("{}", s);
}
我的解答
fn main() {
let s1 = String::from("hello, world");
let s2 = take_ownership(s1);
println!("{}", s2);
}
fn take_ownership(s: String) -> String {
println!("{}", s);
s
}
问题三
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();
s
}
我的解答
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.as_bytes();
s
}
fn main() {
let s = give_ownership();
println!("{}", s);
}
// Only modify the code below!
fn give_ownership() -> String {
let s = String::from("hello, world");
s
}
问题四
// 修复错误,不要删除任何代码行
fn main() {
let s = String::from("hello, world");
print_str(s);
println!("{}", s);
}
fn print_str(s: String) {
println!("{}",s)
}
我的解答
fn main() {
let s = String::from("hello, world");
print_str(&s);
println!("{}", s);
}
fn print_str(s: &String) {
println!("{}", s)
}
fn main() {
let s = String::from("hello, world");
print_str(s.clone());
println!("{}", s);
}
fn print_str(s: String) {
println!("{}",s)
}
问题五
// 不要使用 clone,使用 copy 的方式替代
fn main() {
let x = (1, 2, (), "hello".to_string());
let y = x.clone();
println!("{:?}, {:?}", x, y);
}
我的解答
fn main() {
let x = (1, 2, (), "hello");
let y = x;
println!("{:?}, {:?}", x, y);
}
问题六
当所有权转移时,可变性也可以随之改变
fn main() {
let s = String::from("hello, ");
// 只修改下面这行代码 !
let s1 = s;
s1.push_str("world")
}
我的解答
fn main() {
let s = String::from("hello, ");
let mut s1 = s;
s1.push_str("world")
}
问题七
fn main() {
let x = Box::new(5);
let ... // 完成该行代码,不要修改其它行!
*y = 4;
assert_eq!(*x, 5);
}
我的解答
fn main() {
let x = Box::new(5);
let mut y = Box::new(1);
*y = 4;
assert_eq!(*x, 5);
}
问题八
当解构一个变量时,可以同时使用 move 和引用模式绑定的方式。当这么做时,部分 move 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。
在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。
fn main() {
let t = (String::from("hello"), String::from("world"));
let _s = t.0;
// 仅修改下面这行代码,且不要使用 `_s`
println!("{:?}", t);
}
我的解答
fn main() {
let t = (String::from("hello"), String::from("world"));
let _s = t.0;
println!("{:?}", t.1);
}
问题九
fn main() {
let t = (String::from("hello"), String::from("world"));
// 填空,不要修改其它代码
let (__, __) = __;
println!("{:?}, {:?}, {:?}", s1, s2, t); // -> "hello", "world", ("hello", "world")
}
我的解答
fn main() {
let t = (String::from("hello"), String::from("world"));
let (ref s1, ref s2) = t;
println!("{:?}, {:?}, {:?}", s1, s2, t); // -> "hello", "world", ("hello", "world")
}
fn main() {
let t = (String::from("hello"), String::from("world"));
let (ref s1, ref s2) = t.clone();
println!("{:?}, {:?}, {:?}", s1, s2, t); // -> "hello", "world", ("hello", "world")
}
引用与借用
问题一
引用
fn main() {
let x = 5;
// 填写空白处
let p = __;
println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84
}
我的解答
fn main() {
let x = 5;
// 填写空白处
let p = &x;
println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84
}
问题二
fn main() {
let x = 5;
let y = &x;
// 只能修改以下行
assert_eq!(5, y);
}
我的解答
fn main() {
let x = 5;
let y = &x;
// 只能修改以下行
assert_eq!(5, *y);
}
问题三
// 修复错误
fn main() {
let mut s = String::from("hello, ");
borrow_object(s)
}
fn borrow_object(s: &String) {}
我的解答
// 修复错误
fn main() {
let mut s = String::from("hello, ");
borrow_object(&s)
}
fn borrow_object(s: &String) {}
问题四
// 修复错误
fn main() {
let mut s = String::from("hello, ");
push_str(s)
}
fn push_str(s: &mut String) {
s.push_str("world")
}
我的解答
// 修复错误
fn main() {
let mut s = String::from("hello, ");
push_str(&mut s)
}
fn push_str(s: &mut String) {
s.push_str("world")
}
问题五
fn main() {
let mut s = String::from("hello, ");
// 填写空白处,让代码工作
let p = __;
p.push_str("world");
}
我的解答
fn main() {
let mut s = String::from("hello, ");
// 填写空白处,让代码工作
let p = &mut s;
p.push_str("world");
}
问题六
ref 与 & 类似,可以用来获取一个值的引用,但是它们的用法有所不同。
fn main() {
let c = '中';
let r1 = &c;
// 填写空白处,但是不要修改其它行的代码
let __ r2 = c;
assert_eq!(*r1, *r2);
// 判断两个内存地址的字符串是否相等
assert_eq!(get_addr(r1),get_addr(r2));
}
// 获取传入引用的内存地址的字符串形式
fn get_addr(r: &char) -> String {
format!("{:p}", r)
}
我的解答
fn main() {
let c = '中';
let r1 = &c;
let ref r2 = c;
assert_eq!(*r1, *r2);
assert_eq!(get_addr(r1), get_addr(r2));
}
fn get_addr(r: &char) -> String {
format!("{:p}", r)
}
问题七
借用规则
// 移除代码某个部分,让它工作
// 你不能移除整行的代码!
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
我的解答
// 移除代码某个部分,让它工作
// 你不能移除整行的代码!
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
}
问题八
错误: 从不可变对象借用可变
fn main() {
// 通过修改下面一行代码来修复错误
let s = String::from("hello, ");
borrow_object(&mut s)
}
fn borrow_object(s: &mut String) {}
我的解答
fn main() {
let mut s = String::from("hello, ");
borrow_object(&mut s)
}
fn borrow_object(s: &mut String) {}
fn main() {
let mut s = String::from("hello, ");
borrow_object(&s);
s.push_str("world");
}
fn borrow_object(s: &String) {}
问题九
NLL
// 注释掉一行代码让它工作
fn main() {
let mut s = String::from("hello, ");
let r1 = &mut s;
r1.push_str("world");
let r2 = &mut s;
r2.push_str("!");
println!("{}",r1);
}
我的解答
fn main() {
let mut s = String::from("hello, ");
let r1 = &mut s;
r1.push_str("world");
let r2 = &mut s;
r2.push_str("!");
// println!("{}",r1);
}
问题十
fn main() {
let mut s = String::from("hello, ");
let r1 = &mut s;
let r2 = &mut s;
// 在下面增加一行代码人为制造编译错误:cannot borrow `s` as mutable more than once at a time
// 你不能同时使用 r1 和 r2
}
我的解答
fn main() {
let mut s = String::from("hello, ");
let r1 = &mut s;
let r2 = &mut s;
println!("{}", r1)
}
复合类型
字符串
问题一
正常情况下我们无法使用 str 类型,但是可以使用 &str 来替代
// 修复错误,不要新增代码行
fn main() {
let s: str = "hello, world";
}
我的解答
fn main() {
let s: &str = "hello, world";
}
问题二
如果要使用 str 类型,只能配合 Box。 & 可以用来将 Box<str>
转换为 &str 类型
// 使用至少两种方法来修复错误
fn main() {
let s: Box<str> = "hello, world".into();
greetings(s)
}
fn greetings(s: &str) {
println!("{}",s)
}
我的解答
fn main() {
let s: Box<str> = "hello, world".into();
greetings(&s)
}
fn greetings(s: &str) {
println!("{}", s)
}
fn main() {
let s: Box<&str> = "hello, world".into();
greetings(*s)
}
fn greetings(s: &str) {
println!("{}", s);
}
问题三
String 是定义在标准库中的类型,分配在堆上,可以动态的增长。它的底层存储是动态字节数组的方式(Vec<u8>
),但是与字节数组不同,String 是 UTF-8 编码。
// 填空
fn main() {
let mut s = __;
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
我的解答
fn main() {
let mut s = String::from("");
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
fn main() {
let mut s = String::new();
s.push_str("hello, world");
s.push('!');
assert_eq!(s, "hello, world!");
}
问题四
// 修复所有错误,并且不要新增代码行
fn main() {
let s = String::from("hello");
s.push(',');
s.push(" world");
s += "!".to_string();
println!("{}", s)
}
我的解答
fn main() {
let mut s = String::from("hello");
s.push(',');
s.push_str(" world");
s += "!";
println!("{}", s)
}
问题五
我们可以用 replace 方法来替换指定的子字符串
// 填空
fn main() {
let s = String::from("I like dogs");
// 以下方法会重新分配一块内存空间,然后将修改后的字符串存在这里
let s1 = s.__("dogs", "cats");
assert_eq!(s1, "I like cats")
}
我的解答
fn main() {
let s = String::from("I like dogs");
let s1 = s.replace("dogs", "cats");
assert_eq!(s1, "I like cats")
}
问题六
你只能将 String 跟 &str 类型进行拼接,并且 String 的所有权在此过程中会被 move
// 修复所有错误,不要删除任何一行代码
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1 + s2;
assert_eq!(s3,"hello,world!");
println!("{}",s1);
}
我的解答
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1.clone() + &s2;
assert_eq!(s3, "hello,world!");
println!("{}", s1);
}
问题七
我们可以使用两种方法将 &str 转换成 String 类型
// 使用至少两种方法来修复错误
fn main() {
let s = "hello, world";
greetings(s)
}
fn greetings(s: String) {
println!("{}",s)
}
我的解答
fn main() {
let s = "hello, world";
greetings(s)
}
fn greetings(s: &str) {
println!("{}", s)
}
fn main() {
let s = "hello, world".to_string();
greetings(s)
}
fn greetings(s: String) {
println!("{}", s)
}
fn main() {
let s = String::from("hello, world");
greetings(s)
}
fn greetings(s: String) {
println!("{}", s)
}
问题八
我们可以使用 String::from 或 to_string
将 &str 转换成 String 类型
// 使用两种方法来解决错误,不要新增代码行
fn main() {
let s = "hello, world".to_string();
let s1: &str = s;
}
我的解答
fn main() {
let s = "hello, world".to_string();
let s1: &str = &s;
}
fn main() {
let s = "hello, world";
let s1: &str = &s;
}
fn main() {
let s = "hello, world".to_string();
let s1: &str = &s[..];
}
fn main() {
let s = "hello, world".to_string();
let s1: &str = s.as_str();
}
问题九
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 byte_escape = "I'm writing Ru\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
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);
}
问题十
有时候需要转义的字符很多,我们会希望使用更方便的方式来书写字符串: raw string.
/* 填空并修复所有错误 */
fn main() {
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
// 修改上面的行让代码工作
assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
// 如果你希望在字符串中使用双引号,可以使用以下形式
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果希望在字符串中使用 # 号,可以如下使用:
let delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", delimiter);
// 填空
let long_delimiter = __;
assert_eq!(long_delimiter, "Hello, \"##\"")
}
我的解答
fn main() {
let raw_str = "Escapes don't work here: \x3F \u{211D}";
assert_eq!(raw_str, "Escapes don't work here: ? ℝ");
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
let delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", delimiter);
let long_delimiter = r###"Hello, "##""###;
assert_eq!(long_delimiter, "Hello, \"##\"")
}
问题十一
你无法通过索引的方式去访问字符串中的某个字符,但是可以使用切片的方式 &s1[start..end]
,但是 start 和 end 必须准确落在字符的边界处
fn main() {
let s1 = String::from("hi,中国");
let h = s1[0]; // 修改当前行来修复错误,提示: `h` 字符在 UTF-8 格式中只需要 1 个字节来表示
assert_eq!(h, "h");
let h1 = &s1[3..5];// 修改当前行来修复错误,提示: `中` 字符在 UTF-8 格式中需要 3 个字节来表示
assert_eq!(h1, "中");
}
我的解答
fn main() {
let s1 = String::from("hi,中国");
let h = &s1[0..1];
assert_eq!(h, "h");
let h1 = &s1[3..6];
assert_eq!(h1, "中");
}
问题十二
fn main() {
// 填空,打印出 "你好,世界" 中的每一个字符
for c in "你好,世界".__ {
println!("{}", c)
}
}
我的解答
fn main() {
for c in "你好,世界".chars() {
println!("{}", c)
}
}
问题十三
我们可以使用三方库 utf8_slice
来访问 UTF-8 字符串的某个子串,但是与之前不同的是,该库索引的是字符,而不是字节
use utf8_slice;
fn main() {
let s = "The 🚀 goes to the 🌑!";
let rocket = utf8_slice::slice(s, 4, 5);
// 结果是 "🚀",索引的是字符
}
数组
问题一
数组的类型是 [T; Length],就如你所看到的,数组的长度是类型签名的一部分,因此数组的长度必须在编译期就已知
fn main() {
// 使用合适的类型填空
let arr: __ = [1, 2, 3, 4, 5];
// 修改以下代码,让它顺利运行
assert!(arr.len() == 4);
}
我的解答
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
assert!(arr.len() == 5);
}
问题二
fn main() {
// 很多时候,我们可以忽略数组的部分类型,也可以忽略全部类型,让编译器帮助我们推导
let arr0 = [1, 2, 3];
let arr: [_; 3] = ['a', 'b', 'c'];
// 填空
// 数组分配在栈上, `std::mem::size_of_val` 函数会返回整个数组占用的内存空间
// 数组中的每个 char 元素占用 4 字节的内存空间,因为在 Rust 中, char 是 Unicode 字符
assert!(std::mem::size_of_val(&arr) == __);
}
我的解答
fn main() {
let arr0 = [1, 2, 3];
let arr: [_; 3] = ['a', 'b', 'c'];
assert!(std::mem::size_of_val(&arr) == 12);
}
问题三
数组中的所有元素可以一起初始化为同一个值
fn main() {
// 填空
let list: [i32; 100] = __ ;
assert!(list[0] == 1);
assert!(list.len() == 100);
}
我的解答
fn main() {
let list: [i32; 100] = [1; 100];
assert!(list[0] == 1);
assert!(list.len() == 100);
}
问题四
数组中的所有元素必须是同一类型
fn main() {
// 修复错误
let _arr = [1, 2, '3'];
}
我的解答
fn main() {
let _arr = [1, 2, 3];
}
问题五
数组的下标索引从 0 开始
fn main() {
let arr = ['a', 'b', 'c'];
let ele = arr[1]; // 只修改此行来让代码工作
assert!(ele == 'a');
}
我的解答
fn main() {
let arr = ['a', 'b', 'c'];
let ele = arr[0];
assert!(ele == 'a');
}
问题六
越界索引会导致代码的 panic
// 修复代码中的错误
fn main() {
let names = [String::from("Sunfei"), "Sunface".to_string()];
// `get` 返回 `Option<T>` 类型,因此它的使用非常安全
let name0 = names.get(0).unwrap();
// 但是下标索引就存在越界的风险了
let _name1 = &names[2];
}
我的解答
fn main() {
let names = [String::from("Sunfei"), "Sunface".to_string()];
let name0 = names.get(0).unwrap();
let _name1 = &names[1];
}
切片
问题一
这里 [i32]
和 str 都是切片类型,但是直接使用它们会造成编译错误,如下代码所示。为了解决,你需要使用切片的引用: &[i32]
,&str
// 修复代码中的错误,不要新增代码行!
fn main() {
let arr = [1, 2, 3];
let s1: [i32] = arr[0..2];
let s2: str = "hello, world" as str;
}
我的解答
fn main() {
let arr = [1, 2, 3];
let s1: &[i32] = &arr[0..2];
let s2: &str = "hello, world";
}
问题二
一个切片引用占用了 2 个字大小的内存空间(从现在开始,为了简洁性考虑,如无特殊原因,我们统一使用切片来特指切片引用)。该切片的第一个字是指向数据的指针,第二个字是切片的长度。字的大小取决于处理器架构,例如在 x86-64 上,字的大小是 64 位也就是 8 个字节,那么一个切片引用就是 16 个字节大小。
切片(引用)可以用来借用数组的某个连续的部分,对应的签名是 &[T]
,大家可以与数组的签名对比下 [T; Length]
。
fn main() {
let arr: [char; 3] = ['中', '国', '人'];
let slice = &arr[..2];
// 修改数字 `8` 让代码工作
// 小提示: 切片和数组不一样,它是引用。如果是数组的话,那下面的 `assert!` 将会通过: '中'和'国'是 char 类型,char 类型是 Unicode 编码,大小固定为 4 字节,两个字符为 8 字节。
assert!(std::mem::size_of_val(&slice) == 8);
}
我的解答
fn main() {
let arr: [char; 3] = ['中', '国', '人'];
let slice = &arr[..2];
// 16 是切片大小
assert!(std::mem::size_of_val(&slice) == 16);
// 8 是数组大小
assert!(std::mem::size_of_val(slice) == 8);
}
问题三
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// 填空让代码工作起来
let slice: __ = __;
assert_eq!(slice, &[2, 3, 4]);
}
我的解答
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// let slice: &[i32] = &arr[1..=3];
let slice: &[i32] = &arr[1..4];
assert_eq!(slice, &[2, 3, 4]);
}
问题四
fn main() {
let s = String::from("hello");
let slice1 = &s[0..2];
// 填空,不要再使用 0..2
let slice2 = &s[__];
assert_eq!(slice1, slice2);
}
我的解答
fn main() {
let s = String::from("hello");
let slice1 = &s[0..2];
// let slice2 = &s[..=1];
let slice2 = &s[..2];
assert_eq!(slice1, slice2);
}
问题五
fn main() {
let s = "你好,世界";
// 修改以下代码行,让代码工作起来
let slice = &s[0..2];
assert!(slice == "你");
}
我的解答
fn main() {
let s = "你好,世界";
let slice = &s[0..3];
assert!(slice == "你");
}
问题六
&String 可以被隐式地转换成 &str 类型
// 修复所有错误
fn main() {
let mut s = String::from("hello world");
// 这里, &s 是 `&String` 类型,但是 `first_character` 函数需要的是 `&str` 类型。
// 尽管两个类型不一样,但是代码仍然可以工作,原因是 `&String` 会被隐式地转换成 `&str` 类型,如果大家想要知道更多,可以看看 Deref 章节: https://course.rs/advance/smart-pointer/deref.html
let ch = first_character(&s);
s.clear(); // error!
println!("the first character is: {}", ch);
}
fn first_character(s: &str) -> &str {
&s[..1]
}
我的解答
fn main() {
let mut s = String::from("hello world");
let ch = first_character(&s);
println!("the first character is: {}", ch);
s.clear(); // error!
}
fn first_character(s: &str) -> &str {
&s[..1]
}
元组
问题一
元组中的元素可以是不同的类型。元组的类型签名是(T1, T2, …), 这里 T1, T2 是相对应的元组成员的类型
fn main() {
let _t0: (u8,i16) = (0, -1);
// 元组的成员还可以是一个元组
let _t1: (u8, (i16, u32)) = (0, (-1, 1));
// 填空让代码工作
let t: (u8, __, i64, __, __) = (1u8, 2u16, 3i64, "hello", String::from(", world"));
}
我的解答
fn main() {
let _t0: (u8,i16) = (0, -1);
let _t1: (u8, (i16, u32)) = (0, (-1, 1));
let t: (u8, u16, i64, &str, String) = (1u8, 2u16, 3i64, "hello", String::from(", world"));
}
问题二
可以使用索引来获取元组的成员
// 修改合适的地方,让代码工作
fn main() {
let t = ("i", "am", "sunface");
assert_eq!(t.1, "sunface");
}
我的解答
fn main() {
let t = ("i", "am", "sunface");
assert_eq!(t.2, "sunface");
}
问题三
过长的元组无法被打印输出
// 修复代码错误
fn main() {
let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
println!("too long tuple: {:?}", too_long_tuple);
}
我的解答
fn main() {
let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
println!("too long tuple: {:?}", too_long_tuple);
}
问题四
使用模式匹配来解构元组
fn main() {
let tup = (1, 6.4, "hello");
// 填空
let __ = tup;
assert_eq!(x, 1);
assert_eq!(y, "hello");
assert_eq!(z, 6.4);
}
我的解答
fn main() {
let tup = (1, 6.4, "hello");
let (x, z, y) = tup;
assert_eq!(x, 1);
assert_eq!(y, "hello");
assert_eq!(z, 6.4);
}
问题五
解构式赋值
fn main() {
let (x, y, z);
// 填空
__ = (1, 2, 3);
assert_eq!(x, 3);
assert_eq!(y, 1);
assert_eq!(z, 2);
}
我的解答
fn main() {
let (x, y, z);
(y, z, x) = (1, 2, 3);
assert_eq!(x, 3);
assert_eq!(y, 1);
assert_eq!(z, 2);
}
问题六
元组可以用于函数的参数和返回值
fn main() {
// 填空,需要稍微计算下
let (x, y) = sum_multiply(__);
assert_eq!(x, 5);
assert_eq!(y, 6);
}
fn sum_multiply(nums: (i32, i32)) -> (i32, i32) {
(nums.0 + nums.1, nums.0 * nums.1)
}
我的解答
fn main() {
let (x, y) = sum_multiply((2, 3));
assert_eq!(x, 5);
assert_eq!(y, 6);
}
fn sum_multiply(nums: (i32, i32)) -> (i32, i32) {
(nums.0 + nums.1, nums.0 * nums.1)
}
结构体
问题一
对于结构体,我们必须为其中的每一个字段都指定具体的值
// fix the error
struct Person {
name: String,
age: u8,
hobby: String
}
fn main() {
let age = 30;
let p = Person {
name: String::from("sunface"),
age,
};
}
我的解答
struct Person {
name: String,
age: u8,
hobby: String,
}
fn main() {
let age = 30;
let p = Person {
name: String::from("sunface"),
age,
hobby: String::from("dsa"),
};
}
问题二
单元结构体没有任何字段
struct Unit;
trait SomeTrait {
// ...定义一些行为
}
// 我们并不关心结构体中有什么数据( 字段 ),但我们关心它的行为。
// 因此这里我们使用没有任何字段的单元结构体,然后为它实现一些行为
impl SomeTrait for Unit { }
fn main() {
let u = Unit;
do_something_with_unit(u);
}
// 填空,让代码工作
fn do_something_with_unit(u: __) { }
我的解答
struct Unit;
trait SomeTrait {
}
impl SomeTrait for Unit {}
fn main() {
let u = Unit;
do_something_with_unit(u);
}
fn do_something_with_unit(u: Unit) {}
问题三
元组结构体看起来跟元组很像,但是它拥有一个结构体的名称,该名称可以赋予它一定的意义。由于它并不关心内部数据到底是什么名称,因此此时元组结构体就非常适合
// 填空并修复错误
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let v = Point(__, __, __);
check_color(v);
}
fn check_color(p: Color) {
let (x, _, _) = p;
assert_eq!(x, 0);
assert_eq!(p.1, 127);
assert_eq!(__, 255);
}
我的解答
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let v = Point(0, 127, 255);
check_color(v);
}
fn check_color(p: Point) {
let Point(x, y, z) = p;
assert_eq!(x, 0);
assert_eq!(p.1, 127);
assert_eq!(z, 255);
}
问题四
你可以在实例化一个结构体时将它整体标记为可变的,但是 Rust 不允许我们将结构体的某个字段专门指定为可变的
// 填空并修复错误,不要增加或移除代码行
struct Person {
name: String,
age: u8,
}
fn main() {
let age = 18;
let p = Person {
name: String::from("sunface"),
age,
};
// how can you believe sunface is only 18?
p.age = 30;
// 填空
__ = String::from("sunfei");
}
我的解答
struct Person {
name: String,
age: u8,
}
fn main() {
let age = 18;
let mut p = Person {
name: String::from("sunface"),
age,
};
p.age = 30;
p.name = String::from("sunfei");
}
问题五
使用结构体字段初始化缩略语法可以减少一些重复代码
// 填空
struct Person {
name: String,
age: u8,
}
fn main() {}
fn build_person(name: String, age: u8) -> Person {
Person {
age,
__
}
}
我的解答
struct Person {
name: String,
age: u8,
}
fn main() {}
fn build_person(name: String, age: u8) -> Person {
Person {
age,
name
}
}
问题六
你可以使用结构体更新语法基于一个结构体实例来构造另一个
// 填空,让代码工作
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let u1 = User {
email: String::from("someone@example.com"),
username: String::from("sunface"),
active: true,
sign_in_count: 1,
};
let u2 = set_email(u1);
}
fn set_email(u: User) -> User {
User {
email: String::from("contact@im.dev"),
__
}
}
我的解答
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let u1 = User {
email: String::from("someone@example.com"),
username: String::from("sunface"),
active: true,
sign_in_count: 1,
};
let u2 = set_email(u1);
}
fn set_email(u: User) -> User {
User {
email: String::from("contact@im.dev"),
..u
}
}
问题七
我们可以使用 #[derive(Debug)]
让结构体变成可打印的.
// 填空,让代码工作
#[__]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale), // 打印 debug 信息到标准错误输出 stderr,并将 `30 * scale` 的值赋给 `width`
height: 50,
};
dbg!(&rect1); // 打印 debug 信息到标准错误输出 stderr
println!(__, rect1); // 打印 debug 信息到标准输出 stdout
}
我的解答
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),
height: 50,
};
dbg!(&rect1);
println!("{:#?}", rect1);
}
问题八
当解构一个变量时,可以同时使用 move 和引用模式绑定的方式。当这么做时,部分 move 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。
在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。
fn main() {
#[derive(Debug)]
struct Person {
name: String,
age: Box<u8>,
}
let person = Person {
name: String::from("Alice"),
age: Box::new(20),
};
// 通过这种解构式模式匹配,person.name 的所有权被转移给新的变量 `name`
// 但是,这里 `age` 变量确是对 person.age 的引用, 这里 ref 的使用相当于: let age = &person.age
let Person { name, ref age } = person;
println!("The person's age is {}", age);
println!("The person's name is {}", name);
// Error! 原因是 person 的一部分已经被转移了所有权,因此我们无法再使用它
//println!("The person struct is {:?}", person);
// 虽然 `person` 作为一个整体无法再被使用,但是 `person.age` 依然可以使用
println!("The person's age from person struct is {}", person.age);
}
问题九
// 修复错误
#[derive(Debug)]
struct File {
name: String,
data: String,
}
fn main() {
let f = File {
name: String::from("readme.md"),
data: "Rust By Practice".to_string()
};
let _name = f.name;
// 只能修改这一行
println!("{}, {}, {:?}",f.name, f.data, f);
}
我的解答
#[derive(Debug)]
struct File {
name: String,
data: String,
}
fn main() {
let f = File {
name: String::from("readme.md"),
data: "Rust By Practice".to_string(),
};
let _name = f.name;
println!("{}, {}", _name, f.data);
}
枚举
问题一
在创建枚举时,你可以使用显式的整数设定枚举成员的值
// 修复错误
enum Number {
Zero,
One,
Two,
}
enum Number1 {
Zero = 0,
One,
Two,
}
// C语言风格的枚举定义
enum Number2 {
Zero = 0.0,
One = 1.0,
Two = 2.0,
}
fn main() {
// 通过 `as` 可以将枚举值强转为整数类型
assert_eq!(Number::One, Number1::One);
assert_eq!(Number1::One, Number2::One);
}
我的解答
enum Number {
Zero,
One,
Two,
}
enum Number1 {
Zero = 0,
One,
Two,
}
enum Number2 {
Zero = 0,
One = 1,
Two = 2,
}
fn main() {
assert_eq!(Number::One as u8, Number1::One as u8);
assert_eq!(Number1::One as u8, Number2::One as u8);
}
问题二
枚举成员可以持有各种类型的值
// 填空
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg1 = Message::Move{__}; // 使用x = 1, y = 2 来初始化
let msg2 = Message::Write(__); // 使用 "hello, world!" 来初始化
}
我的解答
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg1 = Message::Move { x: 1, y: 2 };
let msg2 = Message::Write(String::from("hello, world!"));
}
问题三
枚举成员中的值可以使用模式匹配来获取
// 仅填空并修复错误
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::Move{x: 1, y: 2};
if let Message::Move{__} = msg {
assert_eq!(a, b);
} else {
panic!("不要让这行代码运行!");
}
}
我的解答
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::Move { x: 1, y: 1 };
if let Message::Move { x: a, y: b } = msg {
assert_eq!(a, b);
} else {
panic!("不要让这行代码运行!");
}
}
问题四
使用枚举对类型进行同一化
// 填空,并修复错误
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msgs: __ = [
Message::Quit,
Message::Move{x:1, y:3},
Message::ChangeColor(255,255,0)
];
for msg in msgs {
show_message(msg)
}
}
fn show_message(msg: Message) {
println!("{}", msg);
}
我的解答
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msgs = [
Message::Quit,
Message::Move { x: 1, y: 3 },
Message::ChangeColor(255, 255, 0),
];
for msg in msgs {
show_message(msg)
}
}
fn show_message(msg: Message) {
println!("{:#?}", msg);
}
问题五
Rust 中没有 null,我们通过 Option<T>
枚举来处理值为空的情况
// 填空让 `println` 输出,同时添加一些代码不要让最后一行的 `panic` 执行到
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
if let __ = six {
println!("{}", n)
}
panic!("不要让这行代码运行!");
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
__ => None,
__ => Some(i + 1),
}
}
我的解答
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
if let Some(n) = six {
println!("{}", n);
return
}
panic!("不要让这行代码运行!");
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
问题六
使用枚举来实现链表
// 填空,让代码运行
use crate::List::*;
enum List {
// Cons: 链表中包含有值的节点,节点是元组类型,第一个元素是节点的值,第二个元素是指向下一个节点的指针
Cons(u32, Box<List>),
// Nil: 链表中的最后一个节点,用于说明链表的结束
Nil,
}
// 为枚举实现一些方法
impl List {
// 创建空的链表
fn new() -> List {
// 因为没有节点,所以直接返回 Nil 节点
// 枚举成员 Nil 的类型是 List
Nil
}
// 在老的链表前面新增一个节点,并返回新的链表
fn prepend(self, elem: u32) -> __ {
Cons(elem, Box::new(self))
}
// 返回链表的长度
fn len(&self) -> u32 {
match *self {
// 这里我们不能拿走 tail 的所有权,因此需要获取它的引用
Cons(_, __ tail) => 1 + tail.len(),
// 空链表的长度为 0
Nil => 0
}
}
// 返回链表的字符串表现形式,用于打印输出
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
// 递归生成字符串
format!("{}, {}", head, tail.__())
},
Nil => {
format!("Nil")
},
}
}
}
fn main() {
// 创建一个新的链表(也是空的)
let mut list = List::new();
// 添加一些元素
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
// 打印列表的当前状态
println!("链表的长度是: {}", list.len());
println!("{}", list.stringify());
}
我的解答
use crate::List::*;
enum List {
Cons(u32, Box<List>),
Nil,
}
impl List {
fn new() -> List {
Nil
}
fn prepend(self, elem: u32) -> List {
Cons(elem, Box::new(self))
}
fn len(&self) -> u32 {
match *self {
Cons(_, ref tail) => 1 + tail.len(),
Nil => 0
}
}
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}
fn main() {
let mut list = List::new();
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
println!("链表的长度是: {}", list.len());
println!("{}", list.stringify());
}
流程控制
问题一
// 填空
fn main() {
let n = 5;
if n < 0 {
println!("{} is negative", n);
} __ n > 0 {
println!("{} is positive", n);
} __ {
println!("{} is zero", n);
}
}
我的解答
// 填空
fn main() {
let n = 5;
if n < 0 {
println!("{} is negative", n);
} else if n > 0 {
println!("{} is positive", n);
} else {
println!("{} is zero", n);
}
}
问题二
if / else 可以用作表达式来进行赋值
// 修复错误
fn main() {
let n = 5;
let big_n =
if n < 10 && n > -10 {
println!("数字太小,先增加 10 倍再说");
10 * n
} else {
println!("数字太大,我们得让它减半");
n / 2.0;
}
println!("{} -> {}", n, big_n);
}
我的解答
// 修复错误
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);
}
问题三
for in 可以用于迭代一个迭代器,例如序列 a..b
fn main() {
for n in 1..=100 { // 修改此行,让代码工作
if n == 100 {
panic!("NEVER LET THIS RUN")
}
}
}
我的解答
fn main() {
for n in 1..100 { // 修改此行,让代码工作
if n == 100 {
panic!("NEVER LET THIS RUN")
}
}
}
问题四
// 修复错误,不要新增或删除代码行
fn main() {
let names = [String::from("liming"),String::from("hanmeimei")];
for name in names {
// do something with name...
}
println!("{:?}", names);
let numbers = [1, 2, 3];
// numbers 中的元素实现了 Copy,因此无需转移所有权
for n in numbers {
// do something with name...
}
println!("{:?}", numbers);
}
我的解答
fn main() {
let names = [String::from("liming"), String::from("hanmeimei")];
for name in &names {
// do something with name...
}
println!("{:?}", names);
let numbers = [1, 2, 3];
// numbers 中的元素实现了 Copy,因此无需转移所有权
for n in numbers {
// do something with name...
}
println!("{:?}", numbers);
}
问题五
fn main() {
let a = [4,3,2,1];
// 通过索引和值的方式迭代数组 `a`
for (i,v) in a.__ {
println!("第{}个元素是{}",i+1,v);
}
}
我的解答
fn main() {
let a = [4, 3, 2, 1];
for (i, v) in a.iter().enumerate() {
println!("第{}个元素是{}", i + 1, v);
}
}
问题六
当条件为 true 时,while 将一直循环
// 填空,让最后一行的 println! 工作 !
fn main() {
// 一个计数值
let mut n = 1;
// 当条件为真时,不停的循环
while n __ 10 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
__;
}
println!("n 的值是 {}, 循环结束",n);
}
我的解答
fn main() {
let mut n = 1;
while n < 10 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
n += 1;
}
println!("n 的值是 {}, 循环结束", n);
}
问题七
使用 break 可以跳出循环
// 填空,不要修改其它代码
fn main() {
let mut n = 0;
for i in 0..=100 {
if n == 66 {
__
}
n += 1;
}
assert_eq!(n, 66);
}
我的解答
fn main() {
let mut n = 0;
for i in 0..=100 {
if n == 66 {
break;
}
n += 1;
}
assert_eq!(n, 66);
}
问题八
continue 会结束当次循环并立即开始下一次循环
// 填空,不要修改其它代码
fn main() {
let mut n = 0;
for i in 0..=100 {
if n != 66 {
n + =1;
__;
}
__
}
assert_eq!(n, 66);
}
我的解答
// 填空,不要修改其它代码
fn main() {
let mut n = 0;
for i in 0..=100 {
if n != 66 {
n += 1;
continue;
}
}
assert_eq!(n, 66);
}
问题九
loop 一般都需要配合 break 或 continue 一起使用
// 填空,不要修改其它代码
fn main() {
let mut count = 0u32;
println!("Let's count until infinity!");
// 无限循环
loop {
count += 1;
if count == 3 {
println!("three");
// 跳过当此循环的剩余代码
__;
}
println!("{}", count);
if count == 5 {
println!("OK, that's enough");
__;
}
}
assert_eq!(count, 5);
}
我的解答
fn main() {
let mut count = 0u32;
println!("Let's count until infinity!");
// 无限循环
loop {
count += 1;
if count == 3 {
println!("three");
// 跳过当前此循环的剩余代码
continue;
}
println!("{}", count);
if count == 5 {
println!("OK, that's enough");
break;
}
}
assert_eq!(count, 5);
}
问题十
loop 是一个表达式,因此我们可以配合 break 来返回一个值
// 填空
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
__;
}
};
assert_eq!(result, 20);
}
我的解答
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
assert_eq!(result, 20);
}
问题十一
当有多层循环时,你可以使用 continue 或 break 来控制外层的循环。要实现这一点,外部的循环必须拥有一个标签 'label
,然后在 break 或 continue 时指定该标签
// 填空
fn main() {
let mut count = 0;
'outer: loop {
'inner1: loop {
if count >= 20 {
// 这只会跳出 inner1 循环
break 'inner1; // 这里使用 `break` 也是一样的
}
count += 2;
}
count += 5;
'inner2: loop {
if count >= 30 {
break 'outer;
}
continue 'outer;
}
}
assert!(count == __)
}
我的解答
fn main() {
let mut count = 0;
'outer: loop {
'inner1: loop {
if count >= 20 {
break 'inner1;
}
count += 2;
}
count += 5;
'inner2: loop {
if count >= 30 {
break 'outer;
}
continue 'outer;
}
}
assert!(count == 30)
}
// TODO 练习到这里
模式匹配
match 和 if let
问题一
// 填空
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
__ => { // 在这里匹配 South 或 North
println!("South or North");
},
_ => println!(__),
};
}