![](/img/trans.png)
[英]Why do str[5], str[] = “1234” and str = malloc(5) have different sizes?
[英]Why do &str arrays in Rust passed as parameters have different lifetimes?
我正在学习 Rust 并且正在测试通过函数进行的一些数组复制。 我确信有内置的 Rust 函数来复制/克隆数组信息,但是我认为个人实现是一个好主意,可以帮助我理解通过函数传递引用。
fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60]) {
// copy 1 into 2
for i in 0..60 {
a2[i] = a1[i];
} // change is reflected in a2 as it is passed as &mut
}
但是,这引发了错误, these two types are declared with different lifetimes...
对于&str
类型本身。 经过一些进一步的学习,我尝试声明自己的一生并将它们分配给它,然后就解决了!
fn copy_str_arr_fix<'a> (a1: [&'a str; 60], a2: &mut [&'a str; 60]) {
// copy 1 into 2
for i in 0..60 {
a2[i] = a1[i];
} // change is reflected in a2 as it is passed as &mut
}
但是,为什么会这样呢? 为什么数组中的值类型需要分配生命周期而不是参数本身? 换句话说,为什么这根本不起作用?
fn copy_str_arr_bad<'a> (a1: &'a [&str; 60], a2: &'a mut [&str; 60]) {
// does not work... ^-----------------------^-------- different lifetimes
for i in 0..60 {
a2[i] = a1[i];
}
}
我仍在努力了解生命周期在更复杂的对象(如数组和结构)的上下文中是如何工作的,因此将不胜感激任何解释!
错误消息有点令人困惑,因为它指的是根据生命周期省略规则生成的生命周期。 在您的情况下,终身省略意味着:
fn copy_str_arr_original(a1: [&str; 60], a2: &mut [&str; 60])
是语法糖:
fn copy_str_arr_original<'a1, 'a2_mut, 'a2>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60])
换句话说,我们有三个完全不相关的生命。 “不相关”意味着调用者可以选择与他们相关联的对象存活多长时间。 例如, a2
的字符串可能是静态的并且一直存在到程序结束,而a1
的字符串可能会在copy_str_arr_original()
返回后立即删除。 或者反过来。 如果这对您来说是个问题,那么您就在正确的轨道上,因为借用检查员同意您的意见。
请注意,有点违反直觉, 'a2_mut
生命周期的长度完全无关,它可以根据调用者的喜好长或短。 我们的函数已经收到引用,因此可以在函数的作用域内使用它。 'a2_mut
生命周期告诉我们它将在函数范围之外存活多久,我们只是不关心这个。
'a1
和'a2
是另一回事。 由于我们将引用从a1
复制到a2
,我们有效地将a1
的引用(类型为&'a1 str
)转换为存储在a2
的引用类型(即&'a2 str
):
a2[i] = a1[i]; // cast &'a1 str to &'a2 str
为了使其有效, &'a1 str
必须是&'a2 str
的子类型。 虽然 Rust 没有 C++ 意义上的类和子类,但它确实有涉及生命周期的子类型。 从这个意义上说,如果 A 值的值保证至少与 B 的值一样长,则 A 是 B 的子类型。换句话说, 'a1
必须至少离开'a2
,这表示为'a1: a2
。 所以这编译:
fn copy_str_arr<'a1: 'a2, 'a2, 'a2_mut>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60]) {
for i in 0..60 {
a2[i] = a1[i];
}
}
强制转换成功的另一种方法是只要求生命周期相同,您在代码中有效地做到了这一点。 (您还省略了'a2_mut
生命周期,编译器正确地将其解释为对无关匿名生命周期的请求。)
假设您可以使用两个不同的、不相关的生命周期定义copy_str_arr
,如下所示:
fn copy_str_arr<'a, 'b>(a1: [&'a str; 60], a2: &mut [&'b str; 60]) {
// ...
}
然后考虑这个例子:
let mut outer: [&str; 60] = [""; 60];
{
let temp_string = String::from("temporary string");
let inner: [&str; 60] = [&temp_string; 60];
// this compiles because our bad `copy_str_arr` function allows
// `inner` and `outer` to have unrelated lifetimes
copy_str_array(&inner, &mut outer);
} // <-- `temp_string` destroyed here
// now `outer` contains references to `temp_string` here, which is invalid
// because it has already been destroyed!
println!("{:?}", outer); // undefined behavior! may print garbage, crash your
// program, make your computer catch fire or anything else
如您所见,如果允许a1
和a2
具有完全不相关的生命周期,那么我们最终可能会遇到这样一种情况,其中一个数组包含对无效数据的引用,这是非常糟糕的。
但是,寿命不必相同。 您可以改为要求您正在复制的生命周期比您复制到的生命周期更长(从而确保您不会非法延长引用的生命周期):
fn copy_str_arr<'a, 'b>(a1: &[&'a str; 60], a2: &mut [&'b str; 60])
where
'a: 'b, // 'a (source) outlives 'b (destination)
{
for i in 0..60 {
a2[i] = a1[i];
}
}
简单的答案是编译器不是很聪明。
您不必在每次定义处理引用的函数时都指定一堆生命周期,这仅仅是因为编译器在可能的情况下进行了一些有根据的猜测。 所以它有点聪明,但不是很聪明。
假设您正在编写一个函数,该函数接受对结构的引用并返回对该结构中字段的引用:
struct Book {
pages: u16,
title: String,
}
fn borrow_title(book: &Book) -> &str {
&book.title
}
十分之九它确实是对您传递的参数的引用。 但有时不是:
fn borrow_title(book: &Book) -> &'static str {
if book.pages > 10 {
"Too long..."
} else {
"Not long enough"
}
}
如您所见,您需要指定返回的&str
具有不同的生命周期(在这种情况下,特殊的'static
.
所以既然你说fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60])
,编译器实际上并没有推理你的实现,也不知道a1
中引用的生命周期应该至少是只要a2
中任何引用的生命周期。
至于第二部分,您需要考虑引用只是指向某些数据的指针。 该数据可以包含其他引用。 在这种情况下,重要的是这些其他参考。
这里有 2 个字符串引用数组。 假设您将引用从第一个复制到第二个。 是否通过引用将这些数组传递给函数并不重要。 重要的是,如果第一个数组的所有权被删除,字符串也会被删除。 如果第二个数组仍然持有任何引用,这将导致不安全的内存处理。
为简化起见,让我们考虑只有一个字符串,我们要将值借用到一个数组中,然后将这些借用值复制到另一个数组中,删除第一个数组,然后删除该字符串。 你希望发生什么?
编译器将抛出一个合适的问题,以确保没有对字符串的引用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.