The Rust References and Lifetimes Guide(Rust引用和生命周期指引)
version 0.1
1 Introduction(介绍)
References are one of the more flexible and powerful tools available in Rust. A reference can point anywhere: into the managed or exchange heap, into the stack, and even into the interior of another data structure. A reference is as flexible as a C pointer or C++ reference. However, unlike C and C++ compilers, the Rust compiler includes special static checks that ensure that programs use references safely. Another advantage of references is that they are invisible to the garbage collector, so working with references helps reduce the overhead of automatic memory management.引用是Rust可用的超级复杂和强大的工具之一.引用可以指向任何地方:可管理或者可交换堆, 或者堆栈,甚至可以指向另外一个数据结构的内部.引用和C语言的指针和C++的引用一样复杂. 但是,不像C和C++编译器,Rust编译器包含特殊静态检查用来确保程序安全使用引用. 另外一个有点是引用对垃圾收集器是不可见的,应此使用引用可以减少自动内存回收的开销. Despite their complete safety, a reference's representation at runtime is the same as that of an ordinary pointer in a C program. They introduce zero overhead. The compiler does all safety checks at compile time.
尽管使用引用完全安全,Rust引用在运行时本质上和C程序的一般指针还是相同的 。 它们是零开销的.编译器在编译时间做安全检查. Although references have rather elaborate theoretical underpinnings (region pointers), the core concepts will be familiar to anyone who has worked with C or C++. Therefore, the best way to explain how they are used—and their limitations—is probably just to work through several examples.
虽然引用有相当精巧的理论基础(区域指针),核型概念对曾经用C或C++工作的感觉很熟悉. 因此,最好解释它们如何使用的方法以及它们的局限,可以通过几个例子来说明.
2 By example
References, sometimes known as borrowed pointers, are only valid for a limited duration. References never claim any kind of ownership over the data that they point to: instead, they are used for cases where you would like to use data for a short time.引用有时候也称呼为 借用指针,仅仅在有限的时段内合法。 引用永远不会要求其指向数据的任何形式的所有权:替代的, 它们被用于你只在很短时间内使用数据的地方。 As an example, consider a simple struct type
Point
:考虑这个例子,一个简单的数据结构:Point
struct Point {x: f64, y: f64}We can use this simple definition to allocate points in many different ways. For example, in this code, each of these three local variables contains a point, but allocated in a different place:
我们可以使用这个简单的定义用众多不同的方法来申请点.比如,以下例子代码中,三个局部变量 每个都包含一个点point,但是在不同的地方申请:
let on_the_stack : Point = Point {x: 3.0, y: 4.0}; let managed_box : @Point = @Point {x: 5.0, y: 1.0}; let owned_box : Box<Point> = box Point {x: 7.0, y: 9.0};Suppose we wanted to write a procedure that computed the distance between any two points, no matter where they were stored. For example, we might like to compute the distance between
on_the_stack
and managed_box
, or between
managed_box
and owned_box
. One option is to define a function that takes
two arguments of type Point
—that is, it takes the points by value. But if we
define it this way, calling the function will cause the points to be
copied. For points, this is probably not so bad, but often copies are
expensive. Worse, if the data type contains mutable fields, copying can change
the semantics of your program in unexpected ways. So we'd like to define a
function that takes the points by pointer. We can use references to do
this:假定我们想写一个过程计算任意两个点之间的距离,而不管它们在哪里保存.比如,我们可以计算距离: 在on_the_stack和managed_box之间或者在managed_box和owned_box之间.一个选择是定义函数有两个Point实际参数, 当然它按值传递使用.但是如何我们这样定义函数的话,调用该函数是将造成points的复制.对point来说,数据结构简单, 不是什么太糟糕的事情,但是经常的复制开销就非常昂贵了.更糟糕的是,如果数据类型包含可修改的字段,复制可以以不希望的 方式改变你程序的语义.应此我们想定义一个函数使用指针引用.我们可以使用引用这样做:
fn compute_distance(p1: &Point, p2: &Point) -> f64 { let x_d = p1.x - p2.x; let y_d = p1.y - p2.y; sqrt(x_d * x_d + y_d * y_d) }Now we can call
compute_distance()
in various ways:然后我们可以用几种方法调用compute_distance()
compute_distance(&on_the_stack, managed_box); compute_distance(managed_box, owned_box);Here, the
&
operator takes the address of the variable
on_the_stack
; this is because on_the_stack
has the type Point
(that is, a struct value) and we have to take its address to get a
value. We also call this borrowing the local variable
on_the_stack
, because we have created an alias: that is, another
name for the same data.这里&运算符表示要获取变量on_the_stack的地址;因为变量on_the_stack的类型为Point(结构值) 所以我们必须取得它的地址来得到值.我们叫这种行为为借用(borrowing)局部变量on_the_stack. 可以这样理解我们建立一个别名:同一个数据的另外一个名字. In contrast, we can pass the boxes
managed_box
and owned_box
to
compute_distance
directly. The compiler automatically converts a box like
@Point
or ~Point
to a reference like &Point
. This is another form
of borrowing: in this case, the caller lends the contents of the managed or
owned box to the callee.相反的,我们可以直接传递封箱managed_box(可管理封箱)和owned_box(自有封箱)到compute_distance()函数. 编译器自动转换封箱如@Point或者~Point为&Point形式的引用.这是另外一种形式的借用: 调用者借出可管理的或者自有的封箱的内容给调用被调用者。 Whenever a caller lends data to a callee, there are some limitations on what the caller can do with the original. For example, if the contents of a variable have been lent out, you cannot send that variable to another task. In addition, the compiler will reject any code that might cause the borrowed value to be freed or overwrite its component fields with values of different types (I'll get into what kinds of actions those are shortly). This rule should make intuitive sense: you must wait for a borrower to return the value that you lent it (that is, wait for the reference to go out of scope) before you can make full use of it again.
每当调用者借出数据给被调用者,关于调用者可以对原始数据做什么有一些限制. 比如,如果变量的内容已经被借用了,你就不能将变量送给(send)给另外一个任务. 此外,编译器会拒绝任何造成被借用的值被释放或者它的组件字段被不同类型的值覆盖. (我将会简短展示这些行为).这个规则可以得出直观的推测:你必须等待借用者返回值之后(也就是说,等待引用出了范围) 才可被你完全再次使用.
3 Other uses for the & operator(引用&操作符的其他用法)
In the previous example, the valueon_the_stack
was defined like so:在前面的例子里,on_the_stack的值是这样被定义的:
let on_the_stack: Point = Point {x: 3.0, y: 4.0};This declaration means that code can only pass
Point
by value to other
functions. As a consequence, we had to explicitly take the address of
on_the_stack
to get a reference. Sometimes however it is more
convenient to move the & operator into the definition of on_the_stack
:这个声明意思是代码只能根据值来转递Point.因此,我们必须显示的获取on_the_stack的地址来得到引用. 有时候更常用的方法是将&运算符移到on_the_stack定义的前面.
let on_the_stack2: &Point = &Point {x: 3.0, y: 4.0};Applying
&
to an rvalue (non-assignable location) is just a convenient
shorthand for creating a temporary and taking its address. A more verbose
way to write the same code is:使用&给一个右值(不可复制的位置)是用来建立一个临时变量获取数据的地址更方便的快捷方法; 更累赘的代码方法可以这样写:
let tmp = Point {x: 3.0, y: 4.0}; let on_the_stack2 : &Point = &tmp;
4 Taking the address of fields(获取字段的地址)
As in C, the&
operator is not limited to taking the address of
local variables. It can also take the address of fields or
individual array elements. For example, consider this type definition
for rectangle
:如同在C语言里一样,&运算符不只限于获取局部变量的地址.它也可以取得字段的地址或者个别数组元素的地址. 比如,考虑这个类型rectangle的定义:
struct Point {x: f64, y: f64} // as before struct Size {w: f64, h: f64} // as before struct Rectangle {origin: Point, size: Size}Now, as before, we can define rectangles in a few different ways:
现在,向前一个例子一样,我们也用几种不同的方法来定义rectangles:
let rect_stack = &Rectangle {origin: Point {x: 1.0, y: 2.0}, size: Size {w: 3.0, h: 4.0}}; let rect_managed = @Rectangle {origin: Point {x: 3.0, y: 4.0}, size: Size {w: 3.0, h: 4.0}}; let rect_owned = box Rectangle {origin: Point {x: 5.0, y: 6.0}, size: Size {w: 3.0, h: 4.0}};In each case, we can extract out individual subcomponents with the
&
operator. For example, I could write:每一个例子中,我们都可以使用&预算符来提取独立的子组件.比如,如此写代码:
compute_distance(&rect_stack.origin, &rect_managed.origin);which would borrow the field
origin
from the rectangle on the stack
as well as from the managed box, and then compute the distance between them.我们从堆栈里的rectangle借用origin字段,同样从可管理的封箱借用origin,然后计算它们的距离.
5 Borrowing managed boxes and rooting(借用可管理的封箱及生根)
We’ve seen a few examples so far of borrowing heap boxes, both managed and owned. Up till this point, we’ve glossed over issues of safety. As stated in the introduction, at runtime a reference is simply a pointer, nothing more. Therefore, avoiding C's problems with dangling pointers requires a compile-time safety check.至此我们看到一部分例子关于借用堆封箱 包括可管理的封箱以及自有的封箱. 直到现在,我们忽略了有关安全的问题.诚如介绍,在运行时参考就是一个简单指针,没有其他东西. 因此,避免C语言的问题悬空指针问题就要求编译时间的安全检查. The basis for the check is the notion of lifetimes. A lifetime is a static approximation of the span of execution during which the pointer is valid: it always corresponds to some expression or block within the program. Code inside that expression can use the pointer without restrictions. But if the pointer escapes from that expression (for example, if the expression contains an assignment expression that assigns the pointer to a mutable field of a data structure with a broader scope than the pointer itself), the compiler reports an error. We'll be discussing lifetimes more in the examples to come, and a more thorough introduction is also available.
检查的基本概念就是生命周期.声明周期是静态近似于执行的跨度,在这个时间内指针是合法的: 这个总是对应着程序里的一些表达式或者块.在表达式里代码可以没有约束的使用指针. 如果指针在表达式里被放弃(比如,如果表达式包含一个复制表达式将指针赋予一个数据结构里可变的字段, 该字段的范围比指针本身更广),那么编译器就报告一个错误.我们将会讨论更多的生命周期的列子, 可得到更加深入的解释. When the
&
operator creates a reference, the compiler must
ensure that the pointer remains valid for its entire
lifetime. Sometimes this is relatively easy, such as when taking the
address of a local variable or a field that is stored on the stack:当&运算符建立一个应用,编译器必须确认该指针在其生命周期内保持合法。 有时候这个相对容易,比如当获取一个局部变量或者储存在堆栈字段地址:
struct X { f: int } fn example1() { let mut x = X { f: 3 }; let y = &mut x.f; // -+ L // ... // | } // -+Here, the lifetime of the reference
y
is simply L, the
remainder of the function body. The compiler need not do any other
work to prove that code will not free x.f
. This is true even if the
code mutates x
.这里来看,引用y的生命周期简单的保持在从L开始保存在函数主体里.编译器无需做任何事情证明代码将不会释放x.f. 这一点甚至在代码修改x的时候也值正确的。 The situation gets more complex when borrowing data inside heap boxes:
状态开始变得更加复杂的情况是当在堆封箱里借用数据时:
fn example2() { let mut x = @X { f: 3 }; let y = &x.f; // -+ L // ... // | } // -+In this example, the value
x
is a heap box, and y
is therefore a
pointer into that heap box. Again the lifetime of y
is L, the
remainder of the function body. But there is a crucial difference:
suppose x
were to be reassigned during the lifetime L? If the
compiler isn't careful, the managed box could become unrooted, and
would therefore be subject to garbage collection. A heap box that is
unrooted is one such that no pointer values in the heap point to
it. It would violate memory safety for the box that was originally
assigned to x
to be garbage-collected, since a non-heap
pointer y
still points into it.这个例子里,x的值是个堆封箱(managed box),y是个指针指向堆封箱.当然y的生命周期是L,在函数剩余主体里保持. 但是这里有一个关键的区别:假定x在声明周期L?被重新赋值,如果编译器不仔细,可管理的封箱将变成无根unrooted, 应此导致被垃圾回收.堆封箱无根是说没有一个在堆上指针指向它.这将违反有关原始的封箱x被内存回收的内存安全, 因为虽然没有堆指针,但依然有一个非堆指针y依然指向它.
Note: Our current implementation implements the garbage collector using reference counting and cycle detection.For this reason, whenever an
&
expression borrows the interior of a
managed box stored in a mutable location, the compiler inserts a
temporary that ensures that the managed box remains live for the
entire lifetime. So, the above example would be compiled as if it were
written由于这个理由,无论如何一个&表达式借用可管理的封箱内部并储存在可变的地点,编译器插入 一个临时变量用来确保可管理的封箱在整个生命周期保持存活.因此上面的例子可以编译为如下所写:
fn example2() { let mut x = @X {f: 3}; let x1 = x; let y = &x1.f; // -+ L // ... // | } // -+Now if
x
is reassigned, the pointer y
will still remain valid. This
process is called rooting.现在即使x被重新赋值,指针y依然保持合法.这个处理过程被叫做生根。
6 Borrowing owned boxes(借用自有的封箱)
The previous example demonstrated rooting, the process by which the compiler ensures that managed boxes remain live for the duration of a borrow. Unfortunately, rooting does not work for borrows of owned boxes, because it is not possible to have two references to an owned box.前一个例子演示了rooting生根,这个处理是编译器为了保证可管理的封箱在借用时段内保持存活. 不幸的是,生根(rooting)在借用自有的封箱上不能使用.因为不可能有两个引用到一个自有的封箱. For owned boxes, therefore, the compiler will only allow a borrow if the compiler can guarantee that the owned box will not be reassigned or moved for the lifetime of the pointer. This does not necessarily mean that the owned box is stored in immutable memory. For example, the following function is legal:
对自有的封箱来说,编译器只允许一次借用.如果编译可以担保自有的封箱在声明周期内不会被重新赋值或者移动. 这就意味着没必要储存自有的封箱在不可变内存上.比如,下面的函数是合法的:
fn example3() -> int { let mut x = box Foo {f: 3}; if some_condition() { let y = &x.f; // -+ L return *y; // | } // -+ x = box Foo {f: 4}; // ... }Here, as before, the interior of the variable
x
is being borrowed
and x
is declared as mutable. However, the compiler can prove that
x
is not assigned anywhere in the lifetime L of the variable
y
. Therefore, it accepts the function, even though x
is mutable
and in fact is mutated later in the function.这里同之前一样,内部变量x被借用并且x被声明为可变的.但是编译器可以证明x在变量y的生命周期L内没有被赋值. 因此,它认为这个函数合法,甚至x在函数的后面实际上被修改了。 It may not be clear why we are so concerned about mutating a borrowed variable. The reason is that the runtime system frees any owned box as soon as its owning reference changes or goes out of scope. Therefore, a program like this is illegal (and would be rejected by the compiler):
当然这个例子也不能表明为什么我们那么关心修改一个被借用的变量.理由是运行时系统是尽快的释放自有的封箱, 如果它自己的引用被修改或者出了范围.因此程序看起来是非法的(被编译器拒绝).
fn example3() -> int { let mut x = box X {f: 3}; let y = &x.f; x = box X {f: 4}; // Error reported here. *y }To make this clearer, consider this diagram showing the state of memory immediately before the re-assignment of
x
:为了更加清晰,我们用图表来表明内存的状态,在重新赋值x之前:
Stack Exchange Heap
x +-------------+
| box {f:int} | ----+
y +-------------+ |
| &int | ----+
+-------------+ | +---------+
+--> | f: 3 |
+---------+
Once the reassignment occurs, the memory will look like this:当重新赋值一发生,内存看起来如下:
Stack Exchange Heap
x +-------------+ +---------+
| box {f:int} | -------> | f: 4 |
y +-------------+ +---------+
| &int | ----+
+-------------+ | +---------+
+--> | (freed) |
+---------+
Here you can see that the variable y
still points at the old box,
which has been freed.这里你可以看到变量y依然指向老的封箱,但它已经被释放回收了. In fact, the compiler can apply the same kind of reasoning to any memory that is (uniquely) owned by the stack frame. So we could modify the previous example to introduce additional owned pointers and structs, and the compiler will still be able to detect possible mutations:
事实上,编译器可以应用同样的理由到(唯一的)堆栈框架(frame)所拥有的任何内存. 因此我们可以修改以前的例子来说明额外的自有的指针和结构,编译器依然可以检查到 可能的改变:
fn example3() -> int { struct R { g: int } struct S { f: Box<R> } let mut x = box S {f: box R {g: 3}}; let y = &x.f.g; x = box S {f: box R {g: 4}}; // Error reported here. x.f = box R {g: 5}; // Error reported here. *y }In this case, two errors are reported, one when the variable
x
is
modified and another when x.f
is modified. Either modification would
invalidate the pointer y
.在这个例子里,两个错误被报告,一个变量x被修改时,另外一个x.f被修改时.任何其中一个修改都能造成指针y的报废.
7 Borrowing and enums(借用和枚举)
The previous example showed that the type system forbids any borrowing of owned boxes found in aliasable, mutable memory. This restriction prevents pointers from pointing into freed memory. There is one other case where the compiler must be very careful to ensure that pointers remain valid: pointers into the interior of anenum
.前面的例子显示了类型系统禁止任何自有的封箱的别名借用,内存修改. 这个限制阻止了指针指向一个被释放的内存.这是另外一个编译器必须仔细确保指针合法的例子: 指向一个枚举enum的内部. As an example, let’s look at the following
shape
type that can
represent both rectangles and circles:如例子,shape类型表明矩形和圆周:
struct Point {x: f64, y: f64}; // as before struct Size {w: f64, h: f64}; // as before enum Shape { Circle(Point, f64), // origin, radius Rectangle(Point, Size) // upper-left, dimensions }Now we might write a function to compute the area of a shape. This function takes a reference to a shape, to avoid the need for copying.
现在我们写一个函数计算形状的面积.这个函数使用shape引用,避免复制的开销.
fn compute_area(shape: &Shape) -> f64 { match *shape { Circle(_, radius) => 0.5 * tau * radius * radius, Rectangle(_, ref size) => size.w * size.h } }The first case matches against circles. Here, the pattern extracts the radius from the shape variant and the action uses it to compute the area of the circle. (Like any up-to-date engineer, we use the tau circle constant and not that dreadfully outdated notion of pi).
第一个例子匹配circle.模式从shape变量抽取radius并用它来计算园的面积(作为一个最新潮的工程师,我们使用tau circle constan 而不是可怕过时的圆周率概念) The second match is more interesting. Here we match against a rectangle and extract its size: but rather than copy the
size
struct, we use a by-reference binding to create a pointer to it. In
other words, a pattern binding like ref size
binds the name size
to a pointer of type &size
into the interior of the enum.第二个例子就比较有趣了.这里我们匹配矩形并且抽取它的尺寸size:为了避免值复制size结构,我们使用通过引用 绑定来建立一个指针指向它.换句话说,pattern绑定 ref size 绑定一个名字size作为类型&size 枚举内部的指针. To make this more clear, let's look at a diagram of memory layout in the case where
shape
points at a rectangle:为了更加清晰的说明这个,我们再次用内存布局图表来说明这个例子里shape指向的矩形:
Stack Memory
+-------+ +---------------+
| shape | ------> | rectangle( |
+-------+ | {x: f64, |
| size | -+ | y: f64}, |
+-------+ +----> | {w: f64, |
| h: f64}) |
+---------------+
Here you can see that rectangular shapes are composed of five words of
memory. The first is a tag indicating which variant this enum is
(rectangle
, in this case). The next two words are the x
and y
fields for the point and the remaining two are the w
and h
fields
for the size. The binding size
is then a pointer into the inside of
the shape.你可以看到矩形形状是由5个单词的内存组成.第一个是标记表明这个枚举变量是rectangle. 下面该点的两个单词x和y字段以及size的w和h字段.绑定size 是一个指针指向shape内部。 Perhaps you can see where the danger lies: if the shape were somehow to be reassigned, perhaps to a circle, then although the memory used to store that shape value would still be valid, it would have a different type! The following diagram shows what memory would look like if code overwrote
shape
with a circle:也许你可以明白危险存在在哪里了:如果形状shape被某种给重新赋值了,也许是园,虽然 内存储存shape值是合法的,它却有了一个不同的类型!,下列图表展示了内存也许可以被代码用圆覆盖shape:
Stack Memory
+-------+ +---------------+
| shape | ------> | circle( |
+-------+ | {x: f64, |
| size | -+ | y: f64}, |
+-------+ +----> | f64) |
| |
+---------------+
As you can see, the size
pointer would be pointing at a f64
instead of a struct. This is not good: dereferencing the second field
of a f64
as if it were a struct with two fields would be a memory
safety violation.如你所见size指针指向f64代替了结构,这是不好的:解应用第二个字段f64将它作为有两个字段的结构, 会造成内存安全侵害。 So, in fact, for every
ref
binding, the compiler will impose the
same rules as the ones we saw for borrowing the interior of an owned
box: it must be able to guarantee that the enum
will not be
overwritten for the duration of the borrow. In fact, the compiler
would accept the example we gave earlier. The example is safe because
the shape pointer has type &Shape
, which means "reference to
immutable memory containing a shape
". If, however, the type of that
pointer were &mut Shape
, then the ref binding would be ill-typed.
Just as with owned boxes, the compiler will permit ref
bindings
into data owned by the stack frame even if the data are mutable,
but otherwise it requires that the data reside in immutable memory.因此,实际中对于每一个ref绑定,编译器将使用同样的规则如同先前借用自有的封箱内部: 它必须能担保enum枚举在被借用期间不会被覆盖.实际上,编辑器会接受我们先前的例子. 该例子是安全的因为shape指针有类型&Shape,这意味这引用到包含一个shap的不可变内存. 如果,该指针的类型是&mut Shape,那么该ref绑定是类型不正确的ill-typed.如同自有的封箱,编译器允许ref绑定到 属于堆栈框架的数据,甚至数据是可变的。除此之外它要求数据必须驻留在不可变内存内.
8 Returning references(返回引用)
So far, all of the examples we have looked at, use references in a “downward” direction. That is, a method or code block creates a reference, then uses it within the same scope. It is also possible to return references as the result of a function, but as we'll see, doing so requires some explicit annotation.到目前为止,我们所看的所有的例子,都是在“向下”方向使用引用,就是一个方法或者一个代码块建立一个引用,然后再同一个范围内使用 引用.当然从一个函数返回一个引用也是可能的,但必须需要显式的注解. For example, we could write a subroutine like this:
如例,我们可以这样写子程序:
struct Point {x: f64, y: f64} fn get_x<'r>(p: &'r Point) -> &'r f64 { &p.x }Here, the function
get_x()
returns a pointer into the structure it
was given. The type of the parameter (&'r Point
) and return type
(&'r f64
) both use a new syntactic form that we have not seen so
far. Here the identifier r
names the lifetime of the pointer
explicitly. So in effect, this function declares that it takes a
pointer with lifetime r
and returns a pointer with that same
lifetime.这里函数get_x()返回一个它使用的结构指针.类型参数(&'r Point)以及范围类型&'r f64.二者都使用新的句法形式,我们从没见过. 这里的标识 r 命名了一个显式指针生命周期.效果就是,该函数声明它使用一个生命周期是r的指针并返回一个同样生命周期的指针 In general, it is only possible to return references if they are derived from a parameter to the procedure. In that case, the pointer result will always have the same lifetime as one of the parameters; named lifetimes indicate which parameter that is.
一般来说,只有当引用是派生自过程的参数才有可能返回一个引用. 这种状况下,指针结果总是和其中一个参数有同样的生命周期;通过命名的生命周期指示符 来表明是哪一个参数 In the previous examples, function parameter types did not include a lifetime name. In those examples, the compiler simply creates a fresh name for the lifetime automatically: that is, the lifetime name is guaranteed to refer to a distinct lifetime from the lifetimes of all other parameters.
在前面的例子里,函数参数类型不包括生命周期名字.这些例子里,编译器简单 自动建立一个生命周期新名字:这个生命周期的名字用来保证引用所有其他参数不同的生命周期 Named lifetimes that appear in function signatures are conceptually the same as the other lifetimes we have seen before, but they are a bit abstract: they don’t refer to a specific expression within
get_x()
,
but rather to some expression within the caller of get_x()
. The
lifetime r
is actually a kind of lifetime parameter: it is defined
by the caller to get_x()
, just as the value for the parameter p
is
defined by that caller.命名生命周期在函数签名中出现概念上和我们以前看到的其他生命周期一样,但它们有点抽象: 它们并不在函数get_x()中引用特定的表达式,而是出现在调用者调用get_x()的表达式中. 生命周期实际上是一种生命周期参数:它由调用者来定义 给函数get_x(),参数p的值是由调用者定义的。 In any case, whatever the lifetime of
r
is, the pointer produced by
&p.x
always has the same lifetime as p
itself: a pointer to a
field of a struct is valid as long as the struct is valid. Therefore,
the compiler accepts the function get_x()
.从所有情况来看,无论生命周期怎样,有&p.x产生的指针总是和p自身有同样的生命周期: 一个结构字段的指针是合法性和结构的合法性一样长.因此,编译器接受函数get_x() To emphasize this point, let’s look at a variation on the example, this time one that does not compile:
为了强调这一点,我们看看例子中的变量,此时不能被编译:
struct Point {x: f64, y: f64} fn get_x_sh(p: @Point) -> &f64 { &p.x // Error reported here }Here, the function
get_x_sh()
takes a managed box as input and
returns a reference. As before, the lifetime of the reference
that will be returned is a parameter (specified by the
caller). That means that get_x_sh()
promises to return a reference
that is valid for as long as the caller would like: this is
subtly different from the first example, which promised to return a
pointer that was valid for as long as its pointer argument was valid.这里函数get_x_sh()使用可管理的封箱作为输入然后返回一个引用.如前所述,将被返回的引用 的生命周期是一个参数(由调用者指定).这意味着get_x_sh()承诺返回的引用只要调用者喜欢也是合法的: 这和第一个例子有点微妙的不同,它承诺返回的指针是合法的只要指针参数是合法的。 Within
get_x_sh()
, we see the expression &p.x
which takes the
address of a field of a managed box. The presence of this expression
implies that the compiler must guarantee that, so long as the
resulting pointer is valid, the managed box will not be reclaimed by
the garbage collector. But recall that get_x_sh()
also promised to
return a pointer that was valid for as long as the caller wanted it to
be. Clearly, get_x_sh()
is not in a position to make both of these
guarantees; in fact, it cannot guarantee that the pointer will remain
valid at all once it returns, as the parameter p
may or may not be
live in the caller. Therefore, the compiler will report an error here.在get_x_sh()函数里,我们看到表达式&p.x是从一个可管理的封箱里获取字段的地址. 这个表达式编译器必须保证。只要结果指针是合法的,可管理的封箱将不会被垃圾收集器回收. 但是再次调用get_x_sh()也承诺返回的指针也如调用者所要求的是合法的.很清晰, get_x_sh()无法做到这来年改革保证.实际上,它不能担保被返回的指针和参数p一样保持合法, 或者在调用者里不再存活因此,编译器将报告一个错误. In general, if you borrow a managed (or owned) box to create a reference, it will only be valid within the function and cannot be returned. This is why the typical way to return references is to take references as input (the only other case in which it can be legal to return a reference is if it points at a static constant).
一般的,如果你借用一个可管理的(或者自有的)封箱来建立一个引用,它仅仅在函数里合法而不能被返回. 这就是为什么返回引用的典型方法是使用引用作为输入.唯一一个例外的情况而且是合法的返回一个引用 是它指向一个静态常量)
9 Named lifetimes(命名的生命周期)
Lifetimes can be named and referenced. For example, the special lifetime'static
, which does not go out of scope, can be used to create global
variables and communicate between tasks (see the manual for use cases).生命周期可以被命名和引用.比如,特殊的生命周期'static,不用出范围,就可以用于建立全局的变量 而在任务task之间通讯(可以参考手册)l
9.1 Parameter Lifetimes(参数生命周期)
Named lifetimes allow for grouping of parameters by lifetime. For example, consider this function:命名的生命周期允许根据生命周期来分组参数,比如,考虑这个函数:
fn select<'r, T>(shape: &'r Shape, threshold: f64, a: &'r T, b: &'r T) -> &'r T { if compute_area(shape) > threshold {a} else {b} }This function takes three references and assigns each the same lifetime
r
. In practice, this means that, in the caller, the
lifetime r
will be the intersection of the lifetime of the three
region parameters. This may be overly conservative, as in this
example:该函数使用三个引用并且使用同样的生命周期 r.实际中,对调用者这意味着 生命周期r将使用这三个区域参数的生命周期的交集。这或许过于保守了,如这个例子:
// -+ r fn select_based_on_unit_circle<'r, T>( // |-+ B threshold: f64, a: &'r T, b: &'r T) -> &'r T { // | | // | | let shape = Circle(Point {x: 0., y: 0.}, 1.); // | | select(&shape, threshold, a, b) // | | } // |-+ // -+In this call to
select()
, the lifetime of the first parameter shape
is B, the function body. Both of the second two parameters a
and b
share the same lifetime, r
, which is a lifetime parameter of
select_based_on_unit_circle()
. The caller will infer the
intersection of these two lifetimes as the lifetime of the returned
value, and hence the return value of select()
will be assigned a
lifetime of B. This will in turn lead to a compilation error, because
select_based_on_unit_circle()
is supposed to return a value with the
lifetime r
.在这个调用select(),第一个参数的生命周期是B,函数的主体。参数a和b共享同一个生命周期 r- 函数select_based_on_unit_circle()的生命周期参数.调用者将推断这两个生命周期的交集作为返回值的生命周期. 当然select()的返回值将被赋予生命周期B.这将导致编译错误:因为select_based_on_unit_circle() 假定返回值的生命周期是r. To address this, we can modify the definition of
select()
to
distinguish the lifetime of the first parameter from the lifetime of
the latter two. After all, the first parameter is not being
returned. Here is how the new select()
might look:既然定位了这个问题,我们可以修改select()定义来第一个参数的生命周期来和后面其他两个参数区别开来。
fn select<'r, 'tmp, T>(shape: &'tmp Shape, threshold: f64, a: &'r T, b: &'r T) -> &'r T { if compute_area(shape) > threshold {a} else {b} }Here you can see that
shape
's lifetime is now named tmp
. The
parameters a
, b
, and the return value all have the lifetime r
.
However, since the lifetime tmp
is not returned, it would be more
concise to just omit the named lifetime for shape
altogether:你可以看到shape的生命周期现在命名为tmp.参数a,b以及返回值有同样的生命周期。但是由于生命周期tmp不能被返回, 它可以更加简洁忽略掉shape的命名的生命周期:
fn select<'r, T>(shape: &Shape, threshold: f64, a: &'r T, b: &'r T) -> &'r T { if compute_area(shape) > threshold {a} else {b} }This is equivalent to the previous definition.
这个相当于前面的定义.
9.2 Labeled Control Structures(标记的控制结构)
Named lifetime notation can also be used to control the flow of execution:命名的生命周期符号可以用于控制执行流程:
'h: for i in range(0,10) { 'g: loop { if i % 2 == 0 { continue 'h; } if i == 9 { break 'h; } break 'g; } }
Note: Labelled breaks are not currently supported withinNamed labels are hygienic and can be used safely within macros. See the macros guide section on hygiene for more details.while
loops.
注意:标记的终端现在不支持在while和loops内部.
命名的标注是健康的可以安全的用于宏里.参看宏指引部分有关健康章节更多的细节。
10 Conclusion(总结)
So there you have it: a (relatively) brief tour of the lifetime system. For more details, we refer to the (yet to be written) reference document on references, which will explain the full notation and give more examples.至此,你已经有了相对而言简要的生命周期系统的游历.更多的细节,我们参考(依然在写) 引用文档有关引用部分,将解释所有的符号给出更多例子.