Rust에서 명시적인 수명이 필요한 이유는 무엇입니까?
Rust 책 의 수명 장 을 읽고 있었고 명명 된 / 명시 적 수명에 대해이 예제를 보았습니다.
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
그것은 컴파일러에 의해 억제되는 오류가 있음을 나에게 매우 분명 처리 할 때 use-after-free 에 할당 된 참조가 x
: 내부 범위를 완료, 후 f
때문에 &f.x
무효되고, 할당 된 안된다 x
.
내 문제는 명시 적 수명 을 사용 하지 않고 문제를 쉽게 분석 할 수 있다는 것 입니다. 예를 들어 더 넓은 범위 ( ) 에 대한 참조의 불법 할당을 유추하여 . 'a
x = &f.x;
어떤 경우에 사후 사용 (또는 다른 클래스) 오류를 방지하기 위해 명시적인 수명이 실제로 필요한가?
다른 답변은 모두 명백한 점 ( 명시 적 수명이 필요한 fjh의 구체적인 예 )이 있지만 한 가지 중요한 사항이 누락되었습니다 . 컴파일러가 잘못 설명 했을 때 명시 적 수명이 필요한 이유는 무엇입니까?
이것은 실제로 "컴파일러가이를 유추 할 수있을 때 명시 적 유형이 필요한 이유"와 같은 질문입니다. 가상의 예 :
fn foo() -> _ {
""
}
물론 컴파일러는를 반환한다는 것을 알 수 있습니다 &'static str
. 그래서 프로그래머는 왜 그것을 입력해야합니까?
주된 이유는 컴파일러가 코드의 기능을 볼 수 있지만 의도가 무엇인지 알 수 없기 때문입니다.
함수는 코드 변경으로 인한 영향을 차단하는 자연스러운 경계입니다. 코드에서 수명을 완전히 검사 할 수있게하려면 순진한 모양의 변화가 수명에 영향을 미쳐 멀리있는 함수에서 오류가 발생할 수 있습니다. 이것은 가상의 예가 아닙니다. 내가 이해 한 것처럼 Haskell은 최상위 함수에 형식 유추에 의존 할 때이 문제가 있습니다. 녹은 새싹에서 그 특정 문제를 해결했다.
컴파일러에는 효율성 이점도 있습니다. 유형과 수명을 확인하려면 함수 서명 만 구문 분석하면됩니다. 더 중요한 것은 프로그래머에게 효율성 이점이 있다는 것입니다. 명시적인 수명이 없다면이 기능은 무엇을 하는가?
fn foo(a: &u8, b: &u8) -> &u8
소스를 검사하지 않고는 말할 수 없으며, 이는 수많은 코딩 모범 사례에 위배됩니다.
더 넓은 범위에 대한 참조의 불법 할당을 유추함으로써
범위 는 기본적으로 수명입니다. 좀 더 명확하게, 수명 'a
은 호출 사이트를 기반으로 컴파일 타임에 특정 범위로 특수화 할 수 있는 일반 수명 매개 변수 입니다.
[...] 오류를 방지하기 위해 명시적인 수명이 실제로 필요한가?
전혀. 수명 은 오류를 방지하는 데 필요하지만, 생생한 프로그래머가 가지고있는 것을 보호하려면 명시적인 수명이 필요합니다.
다음 예제를 보자.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
x
}
fn main() {
let x = 12;
let z: &u32 = {
let y = 42;
foo(&x, &y)
};
}
여기에서 명시적인 수명이 중요합니다. 결과 foo
는 첫 번째 인수 ( 'a
) 와 수명이 같으 므로 두 번째 인수보다 수명 이 길기 때문에 컴파일됩니다 . 이는의 서명에서 수명 이름으로 표시됩니다 foo
. foo
컴파일러 호출에서 인수를 전환하면 y
오래 살지 못한다고 불평 할 것입니다 .
error[E0597]: `y` does not live long enough
--> src/main.rs:10:5
|
9 | foo(&y, &x)
| - borrow occurs here
10 | };
| ^ `y` dropped here while still borrowed
11 | }
| - borrowed value needs to live until here
다음 구조의 수명 주석 :
struct Foo<'a> {
x: &'a i32,
}
Foo
인스턴스가 포함하는 참조보다 인스턴스가 수명을 초과하지 않도록 지정합니다 ( x
field).
때문에이 녹 책에서 온 예는 이것을 설명하지 않습니다 f
및 y
변수가 동시에 범위 밖으로 이동합니다.
더 좋은 예는 다음과 같습니다.
fn main() {
let f : Foo;
{
let n = 5; // variable that is invalid outside this block
let y = &n;
f = Foo { x: y };
};
println!("{}", f.x);
}
이제는로 f
지적한 변수보다 실제로 수명을 연장합니다 f.x
.
Note that there are no explicit lifetimes in that piece of code, except the structure definition. The compiler is perfectly able to infer lifetimes in main()
.
In type definitions, however, explicit lifetimes are unavoidable. For example, there is an ambiguity here:
struct RefPair(&u32, &u32);
Should these be different lifetimes or should they be the same? It does matter from the usage perspective, struct RefPair<'a, 'b>(&'a u32, &'b u32)
is very different from struct RefPair<'a>(&'a u32, &'a u32)
.
Now, for simple cases, like the one you provided, the compiler could theoretically elide lifetimes like it does in other places, but such cases are very limited and do not worth extra complexity in the compiler, and this gain in clarity would be at the very least questionable.
The case from the book is very simple by design. The topic of lifetimes is deemed complex.
The compiler cannot easily infer the lifetime in a function with multiple arguments.
Also, my own optional crate has an OptionBool
type with an as_slice
method whose signature actually is:
fn as_slice(&self) -> &'static [bool] { ... }
There is absolutely no way the compiler could have figured that one out.
If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.
Likewise, if a structure holds two references (as two member fields) then a member function of the structure may sometimes return the first reference and sometimes the second one. Again explicit lifetimes prevent such ambiguities.
In a few simple situations, there is lifetime elision where the compiler can infer lifetimes.
I've found another great explanation here: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references.
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.
As a newcomer to Rust, my understanding is that explicit lifetimes serve two purposes.
Putting an explicit lifetime annotation on a function restricts the type of code that may appear inside that function. Explicit lifetimes allow the compiler to ensure that your program is doing what you intended.
If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called. It suffices to have a look at the annotations of functions that are directly called by that piece of code. This makes your program much easier to reason about for you (the compiler), and makes compile times managable.
On point 1., Consider the following program written in Python:
import pandas as pd
import numpy as np
def second_row(ar):
return ar[0]
def work(second):
df = pd.DataFrame(data=second)
df.loc[0, 0] = 1
def main():
# .. load data ..
ar = np.array([[0, 0], [0, 0]])
# .. do some work on second row ..
second = second_row(ar)
work(second)
# .. much later ..
print(repr(ar))
if __name__=="__main__":
main()
which will print
array([[1, 0],
[0, 0]])
This type of behaviour always surprises me. What is happening is that df
is sharing memory with ar
, so when some of the content of df
changes in work
, that change infects ar
as well. However, in some cases this may be exactly what you want, for memory efficiency reasons (no copy). The real problem in this code is that the function second_row
is returning the first row instead of the second; good luck debugging that.
Consider instead a similar program written in Rust:
#[derive(Debug)]
struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);
impl<'a, 'b> Array<'a, 'b> {
fn second_row(&mut self) -> &mut &'b mut [i32] {
&mut self.0
}
}
fn work(second: &mut [i32]) {
second[0] = 1;
}
fn main() {
// .. load data ..
let ar1 = &mut [0, 0][..];
let ar2 = &mut [0, 0][..];
let mut ar = Array(ar1, ar2);
// .. do some work on second row ..
{
let second = ar.second_row();
work(second);
}
// .. much later ..
println!("{:?}", ar);
}
Compiling this, you get
error[E0308]: mismatched types
--> src/main.rs:6:13
|
6 | &mut self.0
| ^^^^^^^^^^^ lifetime mismatch
|
= note: expected type `&mut &'b mut [i32]`
found type `&mut &'a mut [i32]`
note: the lifetime 'b as defined on the impl at 4:5...
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
In fact you get two errors, there is also one with the roles of 'a
and 'b
interchanged. Looking at the annotation of second_row
, we find that the output should be &mut &'b mut [i32]
, i.e., the output is supposed to be a reference to a reference with lifetime 'b
(the lifetime of the second row of Array
). However, because we are returning the first row (which has lifetime 'a
), the compiler complains about lifetime mismatch. At the right place. At the right time. Debugging is a breeze.
The reason why your example does not work is simply because Rust only has local lifetime and type inference. What you are suggesting demands global inference. Whenever you have a reference whose lifetime cannot be elided, it must be annotated.
참고URL : https://stackoverflow.com/questions/31609137/why-are-explicit-lifetimes-needed-in-rust
'Programing' 카테고리의 다른 글
cbegin / cend의 이유는 무엇입니까? (0) | 2020.05.19 |
---|---|
Postman Chrome 애플리케이션에서 양식 데이터 x-www-form-urlencoded와 raw의 차이점은 무엇입니까? (0) | 2020.05.19 |
파이썬에서 최소 플러그인 아키텍처 구축 (0) | 2020.05.19 |
SQL WITH 절 예 (0) | 2020.05.19 |
JavaScript의 addEventListener 메소드와 동등한 jQuery (0) | 2020.05.19 |