乐趣区

关于rust:与-Rust-勾心斗角-终于要标注生命周期了

rskynet 只是一个有着 300 余行代码的小我的项目……或者极其渺小我的项目,所实现的性能是读取记录多面体信息的 OFF 文件,计算多面体的突围球,基于突围球的核心半自动化的生成 POV Ray 场景中的模型和视图文件并交由 povray 解析,为多面体生成指定视角的渲染后果。不过,这些性能简直与本文无关,无关的仅仅是在实现这些性能的过程中,在为 Mesh 构造体定义一个办法时,遇到了须要显式标注生命周期的状况。

问题要简单化

假如有一个构造体

struct Foo {
    name: &str,
    value: i32,
}

若应用以下代码结构 Foo 的实例

let x = Foo {name: "foo", value: 1};

rustc 会无奈地指出

error[E0106]: missing lifetime specifier
  ... ... ...
  |
  |     name: &str,
  |           ^ expected named lifetime parameter

还会给出倡议

elp: consider introducing a named lifetime parameter
  |
  ~ struct Foo<'a> {
  ~     name: &'a str,
  |

依照上述倡议,将 Foo 的定义批改为

struct Foo<'a> {
    name: &'a str,
    value: i32,
}

问题便失去了解决,至此我见证了 Rust 的平凡创造——生命周期标注,然而到底产生了什么?

生命周期是泛型类型

在批改后的 Foo 里,'a 呈现在我相熟的泛型参数 T 呈现的地位,它是泛型参数吗?我猜,是的。万物不同,但工夫都是雷同的。在 Rust 语言里,每个变量都有生命周期。应用过期的变量,会导致代码被 rustc 回绝通过。现实是好的,但 rustc 有时无奈判断一个变量是否过期。例如

let x = Foo {name: "foo", value: 1};

将字符串 "foo" 的援用赋给了 Fooname 成员变量,rustc 无奈确定在 x 的生命周期内,字符串 "foo" 仍然健在。在我看来,"foo" 是间接编码在程序的可执行文件里的,它与程序同寿,齐全没有可能在 x 的生命周期内被开释,然而 在 rustc 看来,它只晓得这是个字符串的援用,而只有是援用,就可能存在援用过期变量的危险,因而它须要我明确通知它,"foo" 能活多久。

生命周期标记

要通知 rustc,一个变量的生命周期是多久,根本是不可能的,然而可能通过生命周期标记通知 rustc,一个变量的生命周期至多与另一个变量相等。例如

struct Foo<'a> {
    name: &'a str,
    value: i32,
}

可能通知 rustc,name 援用的变量至多能活得跟 Foo 的实例一样久,事实上,的确如此。

生命周期标记仅仅是变量援用的时效性给予束缚,它并不能扭转变量的生命周期。上面的代码能够阐明这一点,

let mut x = Foo {name: "", value: 1};
{let a = String::from("foo");
    foo.name = a.as_str();}
println!("({}, {})", foo.name, foo.value);

a{...} 形成的部分作用区域内存活,出了该区域,便会被开释。只管 a 的局部内容(字符串切片)被 foo.name 援用,而且生命周期标记要求该局部内容的生命周期至多与 foo 相等,但理论状况并非如此,因而 rustc 会回绝这段代码通过编译,并给出以下错误信息

error[E0597]: `a` does not live long enough
   ... ... ...
   |
   |         foo.name = a.as_str();
   |                    ^^^^^^^^^^ borrowed value does not live long enough
   |     }
   |     - `a` dropped here while still borrowed
   |     println!("({}, {})", foo.name, foo.value);
   |                          -------- borrow later used here

陈年冤案

去年的上个月在写 rhamal.pdf,在 2.8 节,我遇到了一个灵异事件,即以下代码无奈通过编译:

struct Point {x: f64, y: f64, z: f64}

fn main() {let mut a = Point {x: 1.0, y: 2.0, z: 3.0};
    let b = &mut a;
    println!("({}, {}, {})", a.x, a.y, a.z);
    println!("({}, {}, {})", b.x, b.y, b.z);
}

过后,因为对 Rust 语法过于畏惧,以至没有急躁察看 rustc 的报错,而是想当然地认为这可能跟 ab 的生命周期不统一无关。当初,能够给生命周期翻案了。

上述代码的谬误之处在于,ab 可变借用了,而且是可变借用,而在随后的 println! 语句中,a 是以不可变借用的模式呈现——println! 是一个宏,其参数会主动被转化为不可变借用。在 Rust 语法里,一个变量在被当成可变借用之后,假使对其进行不可变借用,那么就再也不能通过可变援用拜访它了,反之,若对一个变量先进行不可变借用,而后再进行可变借用,这是容许的——大略能够防止数据竞争。因而,上述代码须要批改为

struct Point {x: f64, y: f64, z: f64}

fn main() {let mut a = Point {x: 1.0, y: 2.0, z: 3.0};
    let b = &mut a;
    println!("({}, {}, {})", b.x, b.y, b.z);
    println!("({}, {}, {})", a.x, a.y, a.z);
}

小结

不再太胆怯生命周期标记了。

退出移动版