같은 구조체에 값과 해당 값에 대한 참조를 저장할 수없는 이유는 무엇입니까?
나는 가치가 있고 그 가치와 그 가치 내부에 대한 참조를 내 유형으로 저장하고 싶다.
struct Thing {
count: u32,
}
struct Combined<'a>(Thing, &'a u32);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing { count: 42 };
Combined(thing, &thing.count)
}
때로는 값이 있고 해당 값과 해당 값에 대한 참조를 동일한 구조에 저장하려고합니다.
struct Combined<'a>(Thing, &'a Thing);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing::new();
Combined(thing, &thing)
}
때로는 값을 참조하지 않고 동일한 오류가 발생합니다.
struct Combined<'a>(Parent, Child<'a>);
fn make_combined<'a>() -> Combined<'a> {
let parent = Parent::new();
let child = parent.child();
Combined(parent, child)
}
이러한 각 경우에, "충분히 오래 살지 않는다"는 오류가 발생합니다. 이 오류는 무엇을 의미합니까?
보자 .
struct Parent {
count: u32,
}
struct Child<'a> {
parent: &'a Parent,
}
struct Combined<'a> {
parent: Parent,
child: Child<'a>,
}
impl<'a> Combined<'a> {
fn new() -> Self {
let parent = Parent { count: 42 };
let child = Child { parent: &parent };
Combined { parent, child }
}
}
fn main() {}
오류와 함께 실패합니다.
error[E0515]: cannot return value referencing local variable `parent`
--> src/main.rs:19:9
|
17 | let child = Child { parent: &parent };
| ------- `parent` is borrowed here
18 |
19 | Combined { parent, child }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `parent` because it is borrowed
--> src/main.rs:19:20
|
14 | impl<'a> Combined<'a> {
| -- lifetime `'a` defined here
...
17 | let child = Child { parent: &parent };
| ------- borrow of `parent` occurs here
18 |
19 | Combined { parent, child }
| -----------^^^^^^---------
| | |
| | move out of `parent` occurs here
| returning this value requires that `parent` is borrowed for `'a`
이 오류를 완전히 이해하려면 메모리에 값이 표시되는 방식과 해당 값 을
이동할
때 발생하는 상황에 대해 고려해야 합니다.
Combined::new
값의 위치를 보여주는 가상 메모리 주소로 주석 을 달자 .
let parent = Parent { count: 42 };
// `parent` lives at address 0x1000 and takes up 4 bytes
// The value of `parent` is 42
let child = Child { parent: &parent };
// `child` lives at address 0x1010 and takes up 4 bytes
// The value of `child` is 0x1000
Combined { parent, child }
// The return value lives at address 0x2000 and takes up 8 bytes
// `parent` is moved to 0x2000
// `child` is ... ?
어떻게
child
됩니까? 값이 방금 이동
parent
한 경우 더 이상 유효한 값이 보장되지 않는 메모리를 나타냅니다. 다른 코드는 메모리 주소 0x1000에 값을 저장할 수 있습니다. 정수라고 가정하여 해당 메모리에 액세스하면 충돌 및 / 또는 보안 버그가 발생할 수 있으며 Rust가 방지하는 주요 오류 범주 중 하나입니다.이것은
평생
예방할 수있는 문제입니다 . 수명은 약간의 메타 데이터로,
현재 메모리 위치
에서 값이 얼마나 오래 유효한지 알 수 있습니다 . 그것은 Rust 이민자들이 흔히 저지르는 실수이기 때문에 중요한 차이점입니다. 녹 수명은 객체가 생성 된 시점과 파괴 된 시점 사이의 기간 이
아닙니다
!비유로서, 다음과 같이 생각하십시오 : 사람의 삶 동안, 그들은 각각 다른 주소를 가진 많은 다른 위치에있을 것입니다. Rust의 수명은 미래에 죽을 때마다가 아니라
현재 거주
하는 주소와 관련 이 있습니다 (죽음도 주소를 변경하지만). 주소가 더 이상 유효하지 않으므로 이동할 때마다 관련이 있습니다.또한 수명
이
코드를 변경 하지는 않습니다 . 코드는 수명을 제어하고 수명은 코드를 제어하지 않습니다. 한마디로 말하면 "수명은 규범이 아니라 묘사 적"입니다.
Combined::new
평생을 강조하기 위해 사용할 줄 번호로 주석 을 달자 .
{ // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
The concrete lifetime of parent
is from 1 to 4, inclusive (which I'll represent as [1,4]
). The concrete lifetime of child
is [2,4]
, and the concrete lifetime of the return value is [4,5]
. It's possible to have concrete lifetimes that start at zero - that would represent the lifetime of a parameter to a function or something that existed outside of the block.
Note that the lifetime of child
itself is [2,4]
, but that it refers to a value with a lifetime of [1,4]
. This is fine as long as the referring value becomes invalid before the referred-to value does. The problem occurs when we try to return child
from the block. This would "over-extend" the lifetime beyond its natural length.
This new knowledge should explain the first two examples. The third one requires looking at the implementation of Parent::child
. Chances are, it will look something like this:
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
This uses lifetime elision to avoid writing explicit generic lifetime parameters. It is equivalent to:
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
In both cases, the method says that a Child
structure will be returned that has been parameterized with the concrete lifetime of self
. Said another way, the Child
instance contains a reference to the Parent
that created it, and thus cannot live longer than that Parent
instance.
This also lets us recognize that something is really wrong with our creation function:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
Although you are more likely to see this written in a different form:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
In both cases, there is no lifetime parameter being provided via an argument. This means that the lifetime that Combined
will be parameterized with isn't constrained by anything - it can be whatever the caller wants it to be. This is nonsensical, because the caller could specify the 'static
lifetime and there's no way to meet that condition.
How do I fix it?
The easiest and most recommended solution is to not attempt to put these items in the same structure together. By doing this, your structure nesting will mimic the lifetimes of your code. Place types that own data into a structure together and then provide methods that allow you to get references or objects containing references as needed.
There is a special case where the lifetime tracking is overzealous: when you have something placed on the heap. This occurs when you use a Box<T>
, for example. In this case, the structure that is moved contains a pointer into the heap. The pointed-at value will remain stable, but the address of the pointer itself will move. In practice, this doesn't matter, as you always follow the pointer.
The rental crate or the owning_ref crate are ways of representing this case, but they require that the base address never move. This rules out mutating vectors, which may cause a reallocation and a move of the heap-allocated values.
Examples of problems solved with Rental:
- Is there an owned version of String::chars?
- Returning a RWLockReadGuard independently from a method
- How can I return an iterator over a locked struct member in Rust?
- How to return a reference to a sub-value of a value that is under a mutex?
- How do I store a result using Serde Zero-copy deserialization of a Futures-enabled Hyper Chunk?
- How to store a reference without having to deal with lifetimes?
In other cases, you may wish to move to some type of reference-counting, such as by using Rc
or Arc
.
More information
After moving
parent
into the struct, why is the compiler not able to get a new reference toparent
and assign it tochild
in the struct?
While it is theoretically possible to do this, doing so would introduce a large amount of complexity and overhead. Every time that the object is moved, the compiler would need to insert code to "fix up" the reference. This would mean that copying a struct is no longer a very cheap operation that just moves some bits around. It could even mean that code like this is expensive, depending on how good a hypothetical optimizer would be:
let a = Object::new();
let b = a;
let c = b;
Instead of forcing this to happen for every move, the programmer gets to choose when this will happen by creating methods that will take the appropriate references only when you call them.
A type with a reference to itself
There's one specific case where you can create a type with a reference to itself. You need to use something like Option
to make it in two steps though:
#[derive(Debug)]
struct WhatAboutThis<'a> {
name: String,
nickname: Option<&'a str>,
}
fn main() {
let mut tricky = WhatAboutThis {
name: "Annabelle".to_string(),
nickname: None,
};
tricky.nickname = Some(&tricky.name[..4]);
println!("{:?}", tricky);
}
This does work, in some sense, but the created value is highly restricted - it can never be moved. Notably, this means it cannot be returned from a function or passed by-value to anything. A constructor function shows the same problem with the lifetimes as above:
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
What about Pin
?
Pin
, stabilized in Rust 1.33, has this in the module documentation:
A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
It's important to note that "self-referential" doesn't necessarily mean using a reference. Indeed, the example of a self-referential struct specifically says (emphasis mine):
We cannot inform the compiler about that with a normal reference, since this pattern cannot be described with the usual borrowing rules. Instead we use a raw pointer, though one which is known to not be null, since we know it's pointing at the string.
The ability to use a raw pointer for this behavior has existed since Rust 1.0. Indeed, owning-ref and rental use raw pointers under the hood.
The only thing that Pin
adds to the table is a common way to state that a given value is guaranteed to not move.
See also:
A slightly different issue which causes very similar compiler messages is object lifetime dependency, rather than storing an explicit reference. An example of that is the ssh2 library. When developing something bigger than a test project, it is tempting to try to put the Session
and Channel
obtained from that session alongside each other into a struct, hiding the implementation details from the user. However, note that the Channel
definition has the 'sess
lifetime in its type annotation, while Session
doesn't.
This causes similar compiler errors related to lifetimes.
One way to solve it in a very simple way is to declare the Session
outside in the caller, and then for annotate the reference within the struct with a lifetime, similar to the answer in this Rust User's Forum post talking about the same issue while encapsulating SFTP. This will not look elegant and may not always apply - because now you have two entities to deal with, rather than one that you wanted!
Turns out the rental crate or the owning_ref crate from the other answer are the solutions for this issue too. Let's consider the owning_ref, which has the special object for this exact purpose: OwningHandle
. To avoid the underlying object moving, we allocate it on the heap using a Box
, which gives us the following possible solution:
use ssh2::{Channel, Error, Session};
use std::net::TcpStream;
use owning_ref::OwningHandle;
struct DeviceSSHConnection {
tcp: TcpStream,
channel: OwningHandle<Box<Session>, Box<Channel<'static>>>,
}
impl DeviceSSHConnection {
fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
use std::net::TcpStream;
let mut session = Session::new().unwrap();
let mut tcp = TcpStream::connect(targ).unwrap();
session.handshake(&tcp).unwrap();
session.set_timeout(5000);
session.userauth_password(c_user, c_pass).unwrap();
let mut sess = Box::new(session);
let mut oref = OwningHandle::new_with_fn(
sess,
unsafe { |x| Box::new((*x).channel_session().unwrap()) },
);
oref.shell().unwrap();
let ret = DeviceSSHConnection {
tcp: tcp,
channel: oref,
};
ret
}
}
The result of this code is that we can not use the Session
anymore, but it is stored alongside with the Channel
which we will be using. Because the OwningHandle
object dereferences to Box
, which dereferences to Channel
, when storing it in a struct, we name it as such. NOTE: This is just my understanding. I have a suspicion this may not be correct, since it appears to be quite close to discussion of OwningHandle
unsafety.
One curious detail here is that the Session
logically has a similar relationship with TcpStream
as Channel
has to Session
, yet its ownership is not taken and there are no type annotations around doing so. Instead, it is up to the user to take care of this, as the documentation of handshake method says:
This session does not take ownership of the socket provided, it is recommended to ensure that the socket persists the lifetime of this session to ensure that communication is correctly performed.
It is also highly recommended that the stream provided is not used concurrently elsewhere for the duration of this session as it may interfere with the protocol.
So with the TcpStream
usage, is completely up to the programmer to ensure the correctness of the code. With the OwningHandle
, the attention to where the "dangerous magic" happens is drawn using the unsafe {}
block.
A further and a more high-level discussion of this issue is in this Rust User's Forum thread - which includes a different example and its solution using the rental crate, which does not contain unsafe blocks.
'programing' 카테고리의 다른 글
유 방향 그래프에서 모든 사이클 찾기 (0) | 2020.05.14 |
---|---|
JSONDecodeError : 예상 값 : 1 행 1 열 (문자 0) (0) | 2020.05.14 |
@try-Objective-C에서 catch 블록 (0) | 2020.05.14 |
일상적인 사용을 위해 zsh로 전환 할 가치가 있습니까? (0) | 2020.05.14 |
Firefox 및 / 또는 IE 10에서 HTML 5 입력 유형 = "날짜"를 얻는 방법 (0) | 2020.05.14 |