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
- https://dtolnay.github.io/rust-quiz
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的值开辟空间。
Q1
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
语句中使用的时候,这个临时变量的生命周期会延长到该语句所在的作用域结束。