Rust Quiz
notice
可以注意到中间漏了些题目,比如 7, 这是因为这个题目已经过时了,已经被移除出 Rust Quiz
quick intro
Rust Quiz is a collection of questions designed to test your knowledge of Rust. It covers various topics, including ownership, lifetimes, and concurrency.
Ref
Note
Q1
值得一提的是,这里介绍了一种 Rust 编译器处理语法的细节。
虽然 Rust 中一切皆表达式,但 Rust 遇到 {}
的时候,会把块处理成 stmt
,不会当成后面的二元运算符的左表达式。
比如 {} && true
,Rust 会理解成 {}
和 &&true
两个 stmt,而不是 {} && true
一个 expr 类型的 stmt. 也就是这里 &&
并没有被当成一个二元运算符,而是当成了连续使用的两个 &
一元运算符。
如果想要把一个 block 当作二元运算符的一个算子表达式,可以用括号括起来,比如 ({} && true)
,这样 Rust 就会把 {}
当成一个 expr,把 ({} && true)
整体当成一个 expr 类型的 stmt.
Q2
该题目涉及的知识点和 Q1 类似,Rust 编译器在处理语法的时候会把 {}
当成一个 stmt,而不是一个 expr。所以 {} & S(4)
会被 Rust 编译器理解成两个 stmt: {}
和 &S(4)
,其中 &
没有被理解成 BitAnd 运算符,而是理解成了一元的取引用运算符。
Q3
Rust 中用 const 定义的全局常量,在局部作用域中取&mut 的时候,实际是创建了一个具有该常量值的临时变量,并且&mut 指向该临时变量。 在局部作用域中对 const 定义的全局常量的字段进行修改的时候,会创建一个具有该常量值的临时变量,并不会实际修改该全局常量的值,也就是后面再访问该全局常量时,仍然是原来的值。
Q4
Rust 中使用 b"xx"
语法创建 byte string,也就是类型为 &'static [u8;<n>]
的类型表示,这里面 <n>
代指对应的字符串中字符数量,每个字符看作 ascii 字符。
Rust 中 ..
可以用作表示匹配剩余内容,也可以用于表示一个 RangeFull 类型的 range
所以 b"062"[..][1]
指向的是 [b'0', b'6', b'2']
中的第一个元素 b'6'
,对应 ascll 值为 54.
Q6
Rust 中 assiggnment 语句本身的值是 ()
,也就是 unit 类型。
所以 let x = y = 1;
等价于 let x = (y = 1);
,也就是先执行 y = 1
,然后该语句的值是 ()
,再将 ()
赋值给 x
。
也就是 let x = y = 1;
等价于 y = 1; let x = ();
。
Q8
Rust 中声明宏匹配标点符号的时候要考虑空格,有时缺乏空格的情况会按照优先左结合组成标点符号的规律匹配,所以有时有没有空格的匹配结构一致。比如声明宏中 ==>
和 == >
是等价的,都被匹配 ==
和 >
两个标点符号。
Rust 中过程宏的 api 更加灵活,能够准确地处理空格的使用。比如能够区别 ==>
和 == >
。
Q9
Rust 的声明宏的匹配机制中,不同类型的宏的片段指示符(fragment specifiers)可以分为两种,一种会让被捕获的成员不透明,另一种不会。 透明的片段指示符包括:
ident
lifetime
tt
Q10
考察 Rust 中的 method lookup. Rust 中类型的自有方法和类型实现的 trait 方法同名的情况下:
- 方法的接收器不同时,
&self
比&mut self
优先 - 方法的接收器相同时, 类型的自有方法比 trait 方法优先
本题目为特殊情况,对于 dyn Trait 类型,存在 dyn Trait 的自有方法与该 Trait 提供的方法同名的情况下,当前没有办法调用 到该 dyn Trait 类型的自有方法。
Q11
考察 Rust 中泛型参数的 early bound 和 late bound
对于生命周期泛型参数, 存在 early bound 和 late bound 两种情况。
如果是 late bound 的生命周期泛型参数,则不能够显式地指定生命周期泛型参数。
其中 fn f<'a>()
中 'a
就是 late bound 的生命周期泛型参数;
其中 fn g<'a:'a>()
中 'a
就是 early bound 的生命周期泛型参数
Q12
考察 Drop 的顺序
Q13
考察 Rust 中 0 大小类型(ZST)的数组中每个元素的地址都是一样的。 ZST 在 Rust 中是一种特殊的抽象,属于编译时才存在的概念。 编译后程序不会为 ZST 的值开辟空间。
Q14
Rust 中的 impl 语句实现的位置不重要,整个程序的 scope 中都可以访问。
Q15
考察 Rust 中整数类型推导的规则。
对于 Rust 中的整数类型的变量,如果它调用了 trait 方法。
首先查询该整数类型本身是否实现了该 trait,比如查询 i32
,该题目中查询到没有,然后查询其他整数类型,查询到 u32
满足该 trait,所以推导该变量为 u32
类型。
如果所有整数类型 T 都没有实现该 trait,则会查询整数类型的 &T
类型是否是实现了该 trait,优先从 &i32
开始。
ps, 这个材料中推荐的 stack overflow 的回答看起来很晦涩,笔者直接放弃。笔者觉得自己这个理解更直接。
经过实验,发现:
- 如果直接整数类型 T 对 trait 的实现存在多个,但是不包含 i32 的实现的话,则编译器会推导整数类型为 i32,然后报错 trait 未实现
- 如果有多个直接整数类型 T 对 trait 的实现,且包含 i32 的实现的话,则正常编译,并且编译器推导该变量为 i32 类型。
- 如果只是存在一个整数类型对该 trait 的实现,则编译器推导该变量为该整数类型。
- 用 T 代指整数类型,如果没有直接整数类型实现该 trait, 但是有且仅有一个
&T
整数类型的引用类型实现该 trait, 则编译器推导该变量为 T 类型 - 如果存在多个&T 对该 trait 的实现,且不包含
&i32
的实现,则编译器推导该变量为 i32 类型,然后报错 trait 未实现
Q16
Rust 中没有 --
和 ++
这样的一元自增/自减运算符。
所以 --x
其实相当于 -(-x)
,也就是先对 x
取负,然后再对结果取负。
Q17
考察 Rust 中的运算符,Rust 中没有自增/自减运算符。
所以若干个 -
或者若干个 +
会相当于用右结合的方式运算。
Q18
Rust 语法中的成员搜索规则是先搜索成员方法,找不到成员方法再搜索成员变量。
Q19
Rust 中 let _ = s;
这个语句不会 move 变量 s
, 也就是 s
如果有 Drop::drop 实现的话,该函数不会在这一行后立刻触发。
Q22
Rust 中 1.
整体表示一个浮点数。
Rust 中宏指示符会把合法数字表示当成一个 token,对应 tt
片段指示符。
Q23
考察 Rust 中 method lookup 的规则。
自由方法和实现的 trait 方法同名的情况下,
如果接收器相同,优先使用接收器为 &self
的方法
如果接收器不同, 优先使用自有方法。
Q24
可以把宏的卫生性理解成一种对局部变量的着色。 对于宏中直接使用的外部局部变量的名称,指向的是宏定义处有定义的局部变量。 至于常量,Rust 中把局部常量当作 items 而不是局部变量,宏使用的外部局部常量的名称指向调用宏时外部的局部常量。
Q25
考察 Rust 的 desconstructing 语法,以及 drop 的时机。 drop 的时机:
- 当一个值被构造出来后没有持有者的时候,会直接 drop;
- 当值的持有者超出作用域的时候,drop.
let S = f()
中 S
不是一个变量名, 而是一个 destructuring 语法中用到的模式,指向类型 struct S
, 这里表示一个不会失败的模式解构,并且绑定新的变量。
Q26
考察迭代器的延迟计算
Q27
&dyn Trait 类型作为参数类型或者参数类型为 T,约束 T:Trait 时参数的行为都是一样的,只能够访问到 Trait 中定义的方法 Rust 中 super trait 不等于 c++ 中的继承,而只是说明一个 trait 要实现必须先实现了某些 trait.
Q28
同 Q25 一样,考察 Drop 的时机
Q29
type M = (i32);
等价于 type M = i32;
Rust 中 一个元素的 tuple 需要结尾加上 ,
来表示, 比如 (T,)
,以与 (T)
区分开来。
Rust 中对于无后缀的整数数字,往往优先认为是 i32
类型的整数。
Q30
考察 Rust 中的方法查找规则。 以及考察 Rust 标准库中的 Rc 实现了 Clone trait 这个知识点。
Q31
考察 Rust 中变量的方法查找顺序。
trait Or { fn f(self); } struct T; impl Or for &T { fn f(self) { print!("1"); } } impl Or for &&&&T { fn f(self) { print!("2"); } } fn main() { let t = T; let wt = &T; // &T let wwt = &&T; // &&T, &&&T, &mut &&T , &T let wwwt = &&&T; // &&&T, &&&&T let wwwwt = &&&&T; // &&&&T let wwwwwt = &&&&&T; // &&&&&T,&&&&&&T, &mut &&&&&T, &&&&T t.f(); // 1 wt.f(); // 1 wwt.f(); // 1 wwwt.f(); // 2 wwwwt.f(); // 2 wwwwwt.f(); // 2 }
Q32
考察 Rust 的 match 语法
xx|yy if bb => {} {..}
这种模式,
相当于
xx if bb => {..},
yy if bb => {..}
Q33 (Notice!!)
Rust 中 ||
优先和后面的表达式组成闭包
所以 || .. .method()
用括号和空格标上顺序应该为 (|| ..) . method()
如果想要表达调用为 ..
实现的 method 方法,应该给 ..
加上括号如下:
|| ((..).method())
Q34
Rust 中函数指针的大小是固定的,是 usize
大小,需要用来存储函数的地址。
但是 Rust 中函数名本身作为值使用时,它本身的类型不是对应函数指针类型,而是与其函数定义本身绑定的一个类型,这个类型特定指向该函数的实现,所以不需要存储函数的地址,所以大小为 0.
举例说明
fn main(){ fn f() {} let a : fn() = f; // a的类型是函数指针类型, 大小为usize的大小 let b = f; // b的类型是函数f的类型(fn f()), 大小为0 println!( "{:?} {:?}", std::mem::size_of_val(&a), std::mem::size_of_val(&b) ); // 输出: 8 0 }
Q35
考察 Rust 中声明宏的卫生性规则。 为了避免声明宏中新定义的变量与外部同作用域下变量命名冲突,即使是同名变量,宏中定义的变量会被当成不同名字的变量处理。
不过如果变量命名是通过宏参数传入的,那么宏中定义的变量会与外部同作用域下变量命名相同。
#![allow(unused)] fn main() { macro_rules! x { ($n:expr) => { let a = X($n); }; ($id:ident,$n:expr) => { let $id = X($n); }; } struct X(u64); impl Drop for X { fn drop(&mut self) { print!("{}", self.0); } } /// 输出: 121 #[test] fn t1() { let a = X(1); // x!(2); x!(a, 2); print!("{}", a.0); } /// 输出: 221 #[test] fn t2() { let a = X(1); x!(a, 2); print!("{}", a.0); } }
Q36
考察 Rust 中闭包的本质。 闭包相当于建立了一个具有闭包对应函数签名的自有方法的结构体。 该闭包捕获的外部变量,相当于结构体的字段。 如果闭包捕获的外部变量的类型都是实现了 Copy trait,则该闭包相当的结构体也相当于 实现了 Copy trait
Q37
考察 Rust 中的临时生命周期延长。
当一个临时变量的引用被 let
/const
/static
语句中使用的时候,这个临时变量的生命周期会延长到该语句所在的作用域结束。