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方法同名的情况下:

  1. 方法的接收器不同时, &self&mut self优先
  2. 方法的接收器相同时, 类型的自有方法比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的回答看起来很晦涩,笔者直接放弃。笔者觉得自己这个理解更直接。

经过实验,发现:

  1. 如果直接整数类型T对trait的实现存在多个,但是不包含i32的实现的话,则编译器会推导整数类型为i32,然后报错trait未实现
  2. 如果有多个直接整数类型T对trait的实现,且包含i32的实现的话,则正常编译,并且编译器推导该变量为i32类型。
  3. 如果只是存在一个整数类型对该trait的实现,则编译器推导该变量为该整数类型。
  4. 用T代指整数类型,如果没有直接整数类型实现该trait, 但是有且仅有一个&T整数类型的引用类型实现该trait, 则编译器推导该变量为T类型
  5. 如果存在多个&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的时机:

  1. 当一个值被构造出来后没有持有者的时候,会直接drop;
  2. 当值的持有者超出作用域的时候,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语句中使用的时候,这个临时变量的生命周期会延长到该语句所在的作用域结束。