Rust中什么时候可以返回函数生成的字符串slice `&str`?

wlzqhblo  于 2023-10-20  发布在  其他
关注(0)|答案(3)|浏览(123)

我已经设法编造了三个场景,对我来说,表面上看起来是一样的,但表现不同,如果有人能向我澄清为什么,我会很感激。
第一个场景无法编译,正如我实际期望的那样:

fn main() {
    let data = get_data();
    println!("{}", data);
}

fn get_data() -> &'static str {
    let x: String = String::from("hello");
    return x.as_str(); // always fails with ownership issue
}

按预期失败:returns a reference to data owned by the current function
然而,接下来的两个让我困惑,因为我有了上述的期望:
这成功了:

fn get_data() -> &'static str { // requires `'static` lifetime
    let x = std::path::Path::new("thing");
    match x.to_str() {
        None => "",
        Some(s) => s,
    }
}

我本以为s在函数中拥有,所以不能返回,但在这种情况下,添加&'static str作为返回类型(否则编译器会不高兴)允许操作。
更令人困惑的是,下面的工作(编译器很高兴),并且不需要'static生存期:

use std::path::Path;

pub fn parent_of(caller_file: &str) -> &str { // `'static` not required ??
    let caller_file_p: &Path = Path::new(caller_file);

    match caller_file_p.parent() {
        None => if caller_file.starts_with("/") { "/" } else { "." },
        Some(p) => match p.to_str() {
            None => {
                eprintln!("Bad UTF-8 sequence specified : '{}'", caller_file);
                std::process::exit(100);
            },
            Some(s) => s,
        }
    }
}

我相信每个场景的细微差别都有一些系统的解释,但我不知道。非常感谢澄清。

dced5bon

dced5bon1#

在第二种情况下,通过Path::new()&'static str(字符串文字)转换为&PathPath::new()保留引用的生存期(因为Path不是一个拥有的类型,而是一个借用的类型,基本上是对其他人拥有的字节的 Package ,无论是PathBufString,还是字符串字面量的二进制本身)。所以你得到&'static PathBuf。通过Path::to_str()将其转换回&str,您将得到&'static str
在第三种情况下,你有字符串字面量(&'static str s)和&Path,生存期为caller_file。如果我们注解生命周期:

pub fn parent_of<'caller_file>(caller_file: &'caller_file str) -> &'caller_file str {
    // `'static` not required ??
    let caller_file_p: &'caller_file Path = Path::new(caller_file);

    match caller_file_p.parent() {
        None => {
            if caller_file.starts_with("/") {
                "/" // `&'static str`
            } else {
                "." // `&'static str`
            }
        }
        Some(p) => match p.to_str() {
            None => {
                eprintln!("Bad UTF-8 sequence specified : '{}'", caller_file);
                std::process::exit(100);
            }
            Some(s) => s, // `s` is `&'caller_file str`
        },
    }
}

通过生存期省略规则,返回类型的生存期被假定为'caller_file。字符串字面量"/""."&'static str,由于'static大于(或等于)任何生存期,因此它们可以收缩为&'caller_file strcaller_file_p&'caller_file Path,而它的to_str()&'caller_file str,所以这和预期的一样。

aamkag61

aamkag612#

@Chayim Friedman已经提供了一个正确的答案,但是,看到还有另一个包含一些误解的答案。因此,我将添加一些关于Rust中静态这个词的各种用法的细节,以澄清现有的答案。

文字

字面量是内建类型的内建构造函数。这些包括(除其他外)数字,字符串(如&str)和这些文字的元组/数组。所有类型的值都是字面量,它们的生存期都是'static(我们稍后会再讨论这个问题)。

static

所有的二进制文件都有一些预分配的内存在二进制文件中,内存的大小写在二进制文件中。在Rust中,此内存区域分配有static项。存储在这些内存区域中的值的类型必须是'static,因为它们与文字一样,只要程序运行就存在。
但是,这些值不一定是字面量:编译器必须能够在编译时评估该存储器区域的内容。字面量都可以在编译时计算(因为没有什么需要计算的),但一般来说,所有的const表达式也可以放在那里。
事实上,通常情况下,一个字面量 * 不 * 分配为static值。最有可能的是,像3这样的文字将被编译成一条汇编指令,在需要的地方生成该文字(如果没有进一步优化的话)。它不是在加载时创建的,然后复制所有其他地方。但是由于static promotion,一个文字(和其他值)可以被提升为static值,也就是说,它 * 将 * 在二进制文件中分配,并且在必要时,分配的区域将被指向/复制。当需要static值时,编译器会自动执行此操作。

'static生命周期绑定

某些类型T'static生存期约束,在Rust中写为T: 'static。这并不意味着任何T类型的值x将与程序的其余部分一样长,甚至可以借用'static的这样一个值(即。&'static x)。事实上,大多数局部变量(即。当一个函数被调用时创建,并且在该函数离开后不再存在)具有静态类型。考虑以下情况:

fn assert_static<T: 'static>(x: T) -> T { x }

fn example() {
    // literal, that may or may not be a static value
    let x = assert_static(3);
    // `x` is bound by a 'static lifetime, but it lives at most for the scope of the function `example`
    let x = assert_static(x);
    // this expression is not computable at compile time, it is allocated on the heap
    // and will not live forever, yet it is bound by a 'static lifetime
    let x = assert_static(String::from("hello"));
    
    // the following fails, because while the type of x is bound by a 'static lifetime, it does not have a 'static lifetime, so x cannot be borrowed for 'static
    // let y: &'static _ = &x;
}

一种考虑类型受'static生存期约束的值的方法是,从现在开始,这些值可以永远存在。它们不一定存在于程序执行的开始,也不一定存在于程序执行的结束。
最后一次借用是非法的原因是,虽然存储在x中的值具有'static的生命周期,但x本身不能永远存在。对于变量来说,没有生命周期这回事(至少,不暴露给程序员)但这个想法是,在任何时候,我都可以将值移出x,并且该特定值可以永远存在,但是x本身不能永远存在,因此任何对存储在x * 到 * 中的值的引用x被限制为最多与x一样长。这就是Box::leak的典型做法。
这也是为什么第一个例子编译失败,而第二个却没有的原因。

8dtrkrch

8dtrkrch3#

字符串“hello”在编译时存储在程序的二进制文件中的某个地方,因此在程序运行时始终存在。这就是静态生命周期的含义。
因此,让你的第一个版本工作的一种方法是这样写:

fn get_data() -> &'static str {
    let x: &'static str = "hello";
    return x;
}

实际上更习惯地写为:

fn get_data() -> &'static str {
    "hello"
}

在您的版本中,您创建了一个新的String,它将这些字符复制到堆上的某个位置,并为您提供该位置的句柄以及有关使用了多少空间以及仍然可用的空间的一些信息。但是需要注意的是,这个句柄拥有这个内存,当句柄消失时,内存位置就不再有效了。
因此,让你的第一个版本工作的另一种方法是这样写:

fn get_data() -> String {
    "hello".to_string()    
}

但是,您创建了一个借用句柄,并让拥有句柄下降,而不是返回拥有句柄。

fn get_data() -> &'static str {
    let x: String = String::from("hello"); // create owning handle
    return x.as_str(); // return borrowing handle
    // owning handle drops and memory is invalid to access using borrowing handle
}

要理解为什么其他两个函数工作,你应该想想为什么这些工作:

fn get_data() -> &'static str {
    "hello"
}

fn pass_on_data(s: &str) -> &str {
    s
}

相关问题