我已经设法编造了三个场景,对我来说,表面上看起来是一样的,但表现不同,如果有人能向我澄清为什么,我会很感激。
第一个场景无法编译,正如我实际期望的那样:
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,
}
}
}
我相信每个场景的细微差别都有一些系统的解释,但我不知道。非常感谢澄清。
3条答案
按热度按时间dced5bon1#
在第二种情况下,通过
Path::new()
将&'static str
(字符串文字)转换为&Path
。Path::new()
保留引用的生存期(因为Path
不是一个拥有的类型,而是一个借用的类型,基本上是对其他人拥有的字节的 Package ,无论是PathBuf
,String
,还是字符串字面量的二进制本身)。所以你得到&'static PathBuf
。通过Path::to_str()
将其转换回&str
,您将得到&'static str
。在第三种情况下,你有字符串字面量(
&'static str
s)和&Path
,生存期为caller_file
。如果我们注解生命周期:通过生存期省略规则,返回类型的生存期被假定为
'caller_file
。字符串字面量"/"
和"."
是&'static str
,由于'static
大于(或等于)任何生存期,因此它们可以收缩为&'caller_file str
。caller_file_p
是&'caller_file Path
,而它的to_str()
是&'caller_file str
,所以这和预期的一样。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
)。事实上,大多数局部变量(即。当一个函数被调用时创建,并且在该函数离开后不再存在)具有静态类型。考虑以下情况:一种考虑类型受
'static
生存期约束的值的方法是,从现在开始,这些值可以永远存在。它们不一定存在于程序执行的开始,也不一定存在于程序执行的结束。最后一次借用是非法的原因是,虽然存储在
x
中的值具有'static
的生命周期,但x
本身不能永远存在。对于变量来说,没有生命周期这回事(至少,不暴露给程序员)但这个想法是,在任何时候,我都可以将值移出x
,并且该特定值可以永远存在,但是x
本身不能永远存在,因此任何对存储在x
* 到 * 中的值的引用x被限制为最多与x
一样长。这就是Box::leak
的典型做法。这也是为什么第一个例子编译失败,而第二个却没有的原因。
8dtrkrch3#
字符串“hello”在编译时存储在程序的二进制文件中的某个地方,因此在程序运行时始终存在。这就是静态生命周期的含义。
因此,让你的第一个版本工作的一种方法是这样写:
实际上更习惯地写为:
在您的版本中,您创建了一个新的
String
,它将这些字符复制到堆上的某个位置,并为您提供该位置的句柄以及有关使用了多少空间以及仍然可用的空间的一些信息。但是需要注意的是,这个句柄拥有这个内存,当句柄消失时,内存位置就不再有效了。因此,让你的第一个版本工作的另一种方法是这样写:
但是,您创建了一个借用句柄,并让拥有句柄下降,而不是返回拥有句柄。
要理解为什么其他两个函数工作,你应该想想为什么这些工作: