Rust闭包返回引用时的规则及与变量本身的关系是什么?

新手上路,请多包涵
fn main() {
    let mut my_string = String::from("Hello, Rust!");
    let mut my_s_ref = &mut my_string;

    let  consume_string =   || {
        my_s_ref 
    };
    consume_string();
    // consume_string();
    println!("闭包外部消费字符串: {}", my_string);
}
fn main() {
    let  my_string = String::from("Hello, Rust!");
    let  my_s_ref = & my_string;

    let  consume_string =   || {
        // my_s_ref.push_str("string");
        my_s_ref 
    };
    consume_string();
    consume_string();
    println!("闭包外部字符串: {}", my_string);
}

以上两段代码,一个返回String的可变引用,一个返回不可变引用,为什么返回可变引用的闭包实现了FnOnce,返回不可变引用的闭包只实现了Fn呢?

rust返回引用时,遵循什么规则?和my_s_ref本身有关还是my_string有关?

阅读 836
avatarAI BotBETA

Rust中闭包返回引用时的规则主要涉及到闭包捕获环境的方式以及Rust的借用规则。

首先,我们需要理解Rust中的借用规则:

  • 不可变引用(&T)可以有多个,只要它们不重叠。
  • 可变引用(&mut T)只能有一个,并且它独占其引用的数据。

对于你的两段代码,我们可以分别进行分析:

第一段代码

fn main() {
    let mut my_string = String::from("Hello, Rust!");
    let mut my_s_ref = &mut my_string;

    let consume_string = || {
        my_s_ref 
    };
    consume_string();
    // consume_string(); // 这里如果取消注释会导致编译错误
    println!("闭包外部消费字符串: {}", my_string);
}

在这段代码中,consume_string闭包捕获了my_s_ref的可变引用。由于my_s_ref是一个可变引用,这意味着consume_string闭包拥有对my_string的独占访问权限。因此,这个闭包只能被调用一次(FnOnce),因为每次调用都会返回一个可变引用,这会导致所有权和借用问题。

当你试图再次调用consume_string()时(即使注释掉了),Rust编译器会阻止你,因为它会尝试再次返回同一个可变引用,这违反了Rust的借用规则。

第二段代码

fn main() {
    let my_string = String::from("Hello, Rust!");
    let my_s_ref = &my_string;

    let consume_string = || {
        // my_s_ref.push_str("string"); // 这会编译失败,因为my_s_ref是不可变的
        my_s_ref 
    };
    consume_string();
    consume_string();
    println!("闭包外部字符串: {}", my_string);
}

在这段代码中,consume_string闭包捕获了my_s_ref的不可变引用。由于my_s_ref是不可变的,它可以被多次返回,不会违反Rust的借用规则。因此,这个闭包实现了Fn trait,这意味着它可以被多次调用。

规则总结

Rust中闭包返回引用时的规则主要基于以下几点:

  1. 如果闭包捕获了环境的可变引用,则闭包只能被调用一次(实现FnOnce)。
  2. 如果闭包捕获了环境的不可变引用,则闭包可以被多次调用(实现Fn)。
  3. 闭包返回的引用必须遵守Rust的借用规则,即在同一时间不能有多个可变引用指向同一数据,也不能有可变引用和不可变引用同时指向同一数据(除非是不可变引用指向同一数据)。

这些规则与my_s_ref本身有关,因为它决定了闭包捕获的是可变引用还是不可变引用。而my_string则是被引用的数据,它的可变性(mutable/immutable)影响了你可以如何通过my_s_ref来访问它。

2 个回答

closure 都是 FnOnce ,Fn 也是 FnOnce

可以被多次调用的会是 FnMutFnMut 也是 FnOnce

可以被多次调用并且不改变“状态”的是 FnFn 既是 FnMut ,也是 FnOnce

注意 &String 可以 Copy,但是 &mut String 不行。所以第一个 my_s_ref 被 Move 了,于是不能多次调用。它就只能是 FnOnce 。但是后一个里面 my_s_ref 可以 Copy ,所以可以反复调用。

closures

The way a closure captures and handles values from the environment affects which traits the closure implements, and traits are how functions and structs can specify what kinds of closures they can use. Closures will automatically implement one, two, or all three of these Fn traits, in an additive fashion, depending on how the closure’s body handles the values:

  1. FnOnce applies to closures that can be called once. All closures implement at least this trait, because all closures can be called. A closure that moves captured values out of its body will only implement FnOnce and none of the other Fn traits, because it can only be called once.
  2. FnMut applies to closures that don’t move captured values out of their body, but that might mutate the captured values. These closures can be called more than once.
  3. Fn applies to closures that don’t move captured values out of their body and that don’t mutate captured values, as well as closures that capture nothing from their environment. These closures can be called more than once without mutating their environment, which is important in cases such as calling a closure multiple times concurrently.

fn main() {
    let mut my_string = String::from("Hello, Rust!");
    let mut my_s_ref = &mut my_string;

    let  consume_string =   || {
        my_s_ref 
    };
    consume_string();
    // consume_string();
    println!("闭包外部消费字符串: {}", my_string);
}

my_s_ref是可变引用,并且闭包捕获了这个值,就有可能在闭包中进行修改,比如下面在闭包中对捕获的这个可变引用拼接新的内容(如果这个闭包在两个以上的地方同时被调用了就违背了可变引用不能有多个引用原则; 所以闭包对可变引用的捕获只能调用一次):

 let swap = "Formosa".to_string();
 my_s_ref.push_str(&swap);
 my_s_ref 

fn main() {
    let  my_string = String::from("Hello, Rust!");
    let  my_s_ref = & my_string;

    let  consume_string =   || {
        // my_s_ref.push_str("string");
        my_s_ref 
    };
    consume_string();
    consume_string();
    println!("闭包外部字符串: {}", my_string);
}

上面是对my_s_ref不变引用,闭包是对my_s_ref不变引用,不变引用不能修改my_s_ref的值,不变引用可以有多个,所以这个闭包多次调用没有问题

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏