理解Rust的Trait对象和不同函数签名中的生命周期注解

6uxekuva  于 2023-10-20  发布在  其他
关注(0)|答案(2)|浏览(102)

我正在学习Rust中的Traits和Trait Object。在Trait chapter中,我解决了第6个练习,与编译器建议的不同。下面的代码定义了两个结构体(SheepCow)和一个trait(Animal)。SheepCow都通过提供自己的noise方法实现来实现Animal trait。
有两个函数,random_animal_ifrandom_animal_match,它们接受一个参数并返回一个对动态trait对象的引用。

struct Sheep {}
struct Cow {}

trait Animal {
    fn noise(&self) -> String;
}

impl Animal for Sheep {
    fn noise(&self) -> String {
        "baaaaah!".to_string()
    }
}

impl Animal for Cow {
    fn noise(&self) -> String {
        "moooooo!".to_string()
    }
}

fn random_animal_if(random_number: f64) -> &'static dyn Animal {
    if random_number < 10.0 {
        &Sheep {}
    } else if random_number > 20.0 {
        &Cow {}
    } else {
        panic!()
    }
}

fn random_animal_match(random_string: &str) -> &dyn Animal {
    match random_string {
        "sheep" => &Sheep {},
        "cow" => &Cow {},
        _ => panic!(),
    }
}

fn main() {
    let animal = random_animal_if(21.0);
    println!("Randomly animal says {}", animal.noise());
    let animal = random_animal_match("sheep");
    println!("Randomly animal says {}", animal.noise());
}

这两个函数都根据输入创建并返回SheepCow对象。其中之一是对浮点数输入使用条件。另一种是在给定的字符串切片上使用模式匹配。逻辑是相同的,但是如果我在random_animal_if返回类型中省略了&'static生存期规范,那么编译器就会抛出这个错误:
错误[E0106]:缺失寿命说明符
有趣的是,如果输入参数类型从f64更改为&str,则可以删除静态生命周期注解。为什么?这两种类型有什么区别?

ej83mcc0

ej83mcc01#

这是因为Rust的lifetime elision rules
如果你有一个函数,它接受一个引用作为参数并返回一个引用,那么编译器会推断输出从输入中借用,看起来像这样:

fn random_animal_match<'a>(random_str: &'a str) -> &'a dyn Animal {

事实上,函数返回非静态引用的唯一方法是从参数中借用返回的数据。
但是,在您的代码中,random_animal_match的主体不会从参数中借用返回值。编译器仍然推断出省略的生存期 *,就好像 * 是这种情况,但实际上返回类型中的生存期总是'static。这意味着函数返回类型中的生存期受到过度限制。如果函数的调用者在输入&str被删除后试图使用返回的&dyn Animal,则会得到编译器错误,尽管这实际上不应该是一个问题:

fn main() {
    let animal = {
        let sheep = String::from("sheep");
        random_animal_match(&sheep)
    }; // - `sheep` dropped here while still borrowed

    // `sheep` does not live long enough
    println!("Randomly animal says {}", animal.noise());
}

为了最大限度地提高这个函数的灵活性,你应该将生存期设置为返回类型'static

fn random_animal_match(random_str: &str) -> &'static dyn Animal {
juzqafwq

juzqafwq2#

&str是一个引用,有一个与之关联的生存期,而f64没有。f64类型由以下函数拥有:它被存储在堆栈上,并将在函数返回时被释放。基础字符串由其他函数拥有,只是被借用。
返回的值也是一个引用。这意味着它必须有一个生命。在某些情况下,不需要指定生存期,因为可以推断出它。有一个基本规则:
如果参数中仅使用了一个生存期(省略与否),则该生存期将分配给所有省略的输出生存期。
f64的例子中,没有lifetime参数,因为它不是一个引用。因此,该规则不适用。在&str的例子中,有一个生命周期参数被忽略了。因此,在参数中使用了一个生命周期,因此规则适用,返回类型获得相同的生命周期。在f64的情况下,该规则不适用,因此没有将生存期分配给返回类型,但这意味着函数签名无效,因为缺少生存期说明符。
现在,这意味着您可以在&str的情况下省略lifetime参数,但它确实改变了语义。推断的生存期将与字符串的生存期相同,这意味着每当字符串被释放时,返回的对动物的引用也将无效。这可能是不方便的,如果目的是保持动物周围的时间比字符串。因此,在这两种情况下,最好将生存期指定为'static

相关问题