< 返回版块

huangjj27 发表于 2021-02-23 20:06

Tags:Rust日报

经典 Rust 面试题六道

今天在电报群由 @wayslog 提出的六道面试题目,wayslog 老师称之为“经典六道”:

  1. RwLock<T> 对想要在多线程下正确使用,T的约束是?
  2. 如下代码:
trait A{ fn foo(&self) -> Self; }
Box<Vec<dyn A>>;

是否可以通过编译?为什么? 3. CloneCopy 的区别是什么? 4. deref 的被调用过程? 5. Rust里如何实现在函数入口和出口自动打印一行日志? 6. Box<dyn (Fn() + Send +'static)> 是什么意思?

读者们又会几道呢~

一个在终端音乐播放器

十分适合在写命令的时候播放自己喜欢的音乐,有节奏才能更快地编写代码~ 仓库地址: https://github.com/Bluemi/musicus_rs

duplex -- 提供双工与半双工特质

双工 Duplex 意味着通道可以同时进行输入及输出。ReadWrite 特质的串流通常需要 &mut self 而不能同时使用读端和写端。除了 Duplex 特质,该库还为实现了 Duplex + Read + Write 特质的类型提供 HalfDuplex 特质的实现,为实现了 Duplex + AsyncRead + AsyncWrite 特质的类型提供 FullDuplex特质的实现。

仓库地址:https://github.com/sunfishcode/duplex

仓库地址:https://github.com/sunfishcode/duplex

评论区

写评论
whjpji 2021-09-12 11:31

过程宏解法不太对吧,如果函数体内有‵return`语句就不行了。在函数入口输出比较容易,出口的话,我觉得用析构函数比较好

compasses 2021-02-24 23:27

同求电报群地址。。:)

eweca-d 2021-02-24 22:24

第五题过程宏的简单实现:

使用方法:

use func_log::func_log;

fn main() {
    let a = "short";
    let b = "longer";
    let mut ret = "";
    unsafe {
        ret = longest(a, b);
    }
    println!("The longest word is {}.", ret);
}

// 叠几个BUFF试试
#[func_log]
pub unsafe fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    println!("This is inner of the function of the longest");
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

过程宏库:

use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemFn};
use quote::quote;

#[proc_macro_attribute]
pub fn func_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let func = parse_macro_input!(item as ItemFn);

    let func_vis = &func.vis;
    let func_block = &func.block;

    let func_decl = &func.sig;

    if func_decl.constness.is_some() || func_decl.asyncness.is_some() {
        panic!("Try to log a function which is const or async.");
    }
    
    let func_unsafety = &func_decl.unsafety;
    let func_fn = &func_decl.fn_token;
    let func_name = &func_decl.ident;
    let func_generics = &func_decl.generics;
    let func_inputs = &func_decl.inputs;
    let func_output = &func_decl.output;

    let func_name_str = func_name.to_string();

    let expanded = quote! {
        #func_vis #func_unsafety #func_fn #func_name #func_generics(#func_inputs) #func_output {
            println!("Enter function: {}.", #func_name_str);
            let ret = #func_block;
            println!("Exit function: {}.", #func_name_str);
            ret
        }
    };

    expanded.into()
}
作者 huangjj27 2021-02-24 20:19

现在公布 @wayslog 提供的答案:

  1. The type parameter T represents the data that this lock protects. It is required that T satisfies Send to be shared across threads and Sync to allow concurrent access through readers.
  2. 不可以,参考object safe 三条规则。
  3. Copy是marker trait,告诉编译器需要move的时候copy。Clone表示拷贝语义,有函数体。不正确的实现Clone可能会导致Copy出BUG。
  4. Deref 是一个trait,由于rust在调用的时候会自动加入正确数量的 * 表示解引用。则,即使你不加入*也能调用到Deref。
  5. 调用处宏调用、声明时用宏声明包裹、proc_macro包裹函数、邪道一点用compiler plugin、llvm插桩等形式进行。(Go:我用snippet也行)
  6. 一个可以被Send到其他线程里的没有参数和返回值的callable对象,即 Closure,同时是 ownershiped,带有static的生命周期,也就说明没有对上下文的引用。
eweca-d 2021-02-24 12:37

我还以为第五题是类似有Drop之类的语言层面提供的机制。你这么一说我豁然开朗了,原来这题目是想要Rust实现一个类似Python的装饰器的函数装饰功能。确实,过程宏可以做到这一点。找到了一个现成的实现:https://github.com/gsingh93/trace。

用法:

#[trace(prefix_enter="[ENTER]", prefix_exit="[EXIT]")]
fn bar((a, b): (i32, i32)) -> i32 {
    println!("I'm in bar!");
    if a == 1 {
        2
    } else {
        b
    }
}

某个输出:

[ENTER] Entering bar(a = 1, b = 2)
I'm in bar!
[EXIT] Exiting bar = 2

--
👇
pfcoder: 5 应该用过程宏实现吧?

pfcoder 2021-02-24 11:24

5 应该用过程宏实现吧?

93996817 2021-02-24 11:23
想到利用drop 和 trait extension 来实现,不知道有没有更好的方法?

pub fn fn_name<T: ?Sized>(_val: &T) -> &'static str {
    std::any::type_name::<T>()
}
struct AutoPrint(String);

impl AutoPrint {
    fn new(fn_name: String) -> Self {
        println!("function {} start!", fn_name);
        AutoPrint(fn_name)
    }
}

impl Drop for AutoPrint {
    fn drop(&mut self) {
        println!("function {} end!", self.0)
    }
}

fn test_m1_1() {
    let _ap = AutoPrint::new("test_m1_1".into());
}

fn test_m1_2(param1: i32) -> i32 {
    let _ap = AutoPrint::new("test_m1_2".into());
    if matches!(param1, 1..=100) {
        return param1;
    }
    println!("more than 100");
    return param1;
}

trait IRun<Out>: std::ops::FnMut() -> Out {
    fn run(&mut self) -> Out {
        println!("{} start **!", fn_name(self));
        let result = self();
        println!("{} end **!", fn_name(self));
        return result;
    }
}
impl<T, Out> IRun<Out> for T where T: FnMut() -> Out {}

pub trait IRun1<Out, P1>: FnMut(P1) -> Out {
    fn run(&mut self, p1: P1) -> Out {
        println!("{} start **!", fn_name(self));
        let result = self(p1);
        println!("{} end **!", fn_name(self));
        return result;
    }
}
impl<T, Out, P1> IRun1<Out, P1> for T where T: FnMut(P1) -> Out {}

pub trait IRun2<Out, P1, P2>: FnMut(P1, P2) -> Out {
    fn run(&mut self, p1: P1, p2: P2) -> Out {
        println!("{} start **!", fn_name(self));
        let result = self(p1, p2);
        println!("{} end **!", fn_name(self));
        return result;
    }
}
impl<T, Out, P1, P2> IRun2<Out, P1, P2> for T where T: FnMut(P1, P2) -> Out {}

fn test_m2_1() {}

fn test_m2_2(param1: i32) -> i32 {
    if matches!(param1, 1..=100) {
        return param1;
    }
    println!("test_m2_2 more than 100");
    return param1;
}

fn test_m2_3(param1: i32, _param2: f32) -> i32 {
    if matches!(param1, 1..=100) {
        return param1;
    }
    println!("test_m2_3 more than 100");
    return param1;
}

fn main() {
    println!("***方法1******");
    test_m1_1();
    let _a = test_m1_2(10);
    let _b = test_m1_2(101);
    //****************************** */
    println!("***方法2******");
    test_m2_1.run();
    let _a = test_m2_2.run(10);
    let _b = test_m2_2.run(101);
    let _c = test_m2_3.run(300, 0.5);
    println!("result _a={} _b={} _c={}", _a, _b, _c);
}


//TomRust结果输出
***方法1******
function test_m1_1 start!   
function test_m1_1 end!     
function test_m1_2 start!   
function test_m1_2 end!     
function test_m1_2 start!   
more than 100
function test_m1_2 end!     
***方法2******
test001::test_m2_1 start **!
test001::test_m2_1 end **!  
test001::test_m2_2 start **!
test001::test_m2_2 end **!  
test001::test_m2_2 start **!
test_m2_2 more than 100     
test001::test_m2_2 end **!  
test001::test_m2_3 start **!
test_m2_3 more than 100     
test001::test_m2_3 end **!  
result _a=10 _b=101 _c=300  
RedPanda 2021-02-24 10:59

刚好刚刚在张汉东老师的书里面5.1 通用概念看到了值语义和引用语义这一块:

被标记为Copy,该类型被标记为值语义 => 其clone方法进行浅复制;
没有被标记Copy,该类型被标记为引用语义=> 其clone方法进行深复制;

不知道这样理解对不对。

Eliot00 2021-02-24 09:29

可以发下电报群地址吗?

johnmave126 2021-02-24 00:32

5可能旨在考察drop的顺序?实现一个new和drop的时候打印日志的guard type来实现?

eweca-d 2021-02-23 23:33

业余新手,开卷强答,抛砖引玉一下:

  1. RwLock<T> 对想要在多线程下正确使用,T的约束是?
  • 必须满足SendSync,前者是表示其可以跨线程,后者表示其用于不同线程的并发读取。
  1. 如下代码:
trait A{ fn foo(&self) -> Self; }
Box<Vec<dyn A>>;

是否可以通过编译?为什么?

  • 不能通过编译。
  • 首先trait object需要traitobject-safe的,这要求Self不能作为返回类型,因为它的类型在运行时才知晓。
  • 而这里的函数会在编译期就被写进vtable,这要求编译器就要知道这函数的返回类型。所以函数返回值需要修改,不能为Self
  • 其次,Vec内包含的内容需要是定长的,而dyn A不能做到这点。所以应该写为Vec<Box<dyn A>>
  1. CloneCopy 的区别是什么?
  • copy标识的数据,在需要move时通常是复制一份副本进去,本身依然存在。
  • 而当一个变量a持有只实现了Clone的数据,传入一个函数时数据是直接move进去的,它会失去这个数据的所有权。
  • Clone这个trait会提供一个方法clone()来保证你想复制变量而不是move变量时,复制整个变量来使用。
  1. deref 的被调用过程?
  • 没太看懂题目的意思,按我自己的理解答一下。
  • 第一种,就是实现了deref的数据类型T,可以通过*T调用deref的函数。
  • 第二种,就是实现了deref的数据类型T,在使用时,假如要求的类型是U
  • 则编译器会尝试deref直到类型不可deref或类型为U
  • 这个过程是在编译期完成的。这个过程反之是不可行的,比如*UT时,无法匹配。
  1. Rust里如何实现在函数入口和出口自动打印一行日志?
  • 这个真是一点不懂了。
  1. Box<dyn (Fn() + Send +'static)> 是什么意思?
  • 首先dyn (Fn() + Send +'static)代表的是一个trait object,它必须是一个函数对象。
  • 实现了Send这个trait以便跨线程,实现了``static`标注其生命周期是整个程序运行时。
  • 使用Box封装是为了让这个对象在编译期有长度。Box是一个在栈上的智能指针,指向函数对象,而函数对象本身被放在堆上。
1 共 11 条评论, 1 页