Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

71%
+3 −0
Q&A What allows a string slice (&str) to outlive its scope?

There are two things going on here. One which technically explains what's going on fully, and another potential misconception you have. For the former, &T implements the Copy trait regardless ...

posted 2y ago by Derek Elkins‭

Answer
#1: Initial revision by user avatar Derek Elkins‭ · 2022-07-05T00:49:40Z (over 2 years ago)
There are two things going on here. One which technically explains what's going on fully, and another potential misconception you have.

For the former, `&T` implements the `Copy` trait regardless of what `T` is. So all that's happening is when `r = inner;` executes it just does a bitwise copy of the *reference* which `inner` is over `r`. It's no different than if `r` and `inner` had been declared as `i64` say. Indeed, you can verify that a move didn't happen by adding code that uses `inner` after the `r = inner;` line. `inner` (trivially) owned a reference to a `str` but it never owned the `str` itself.

The potential misconception is occurrences of string literals don't mean to dynamically allocate (either on the heap or the stack) a string, and then return a reference to it. Instead, the compiler will statically allocate space for the string literal and all occurrences of the string literal will be replaced with references to this statically allocated memory whose lifetime is the whole program. Formally, this is represented by the `'static` lifetime which subsumes, i.e. outlives, all other lifetimes.

Incidentally, there is some terminological complexity here. The reference that `inner` is bound to has a lifetime which, in this example, is the scope containing `inner`. Reference types also contain a lifetime, the `'a` in `&'a T`, and that is how long the reference type requires the referred to data to live. Finally, the actual lifetime of the referred to data can be any lifetime which subsumes the lifetime the reference requires, i.e. a `&'a T` reference can refer to data that has a lifetime of `'b` as long as `'a : 'b`, i.e. `'b` contains `'a`.

In summary, `inner` is a short-lived reference to immortal data. `s` and `r` are slightly longer-lived references to immortal data. `r = inner;` is just a bitwise copy of a pointer, and this is safe because the referred to data, being immortal will outlive any lifetime the reference type requires, i.e. `'a : 'static` for all `'a`.

Playing with the following type can help illustrate what's happening.
```rust
#[derive(Debug)]
struct NoisyDrop {
    i: i64
}
impl Drop for NoisyDrop {
    fn drop(&mut self) {
        println!("Dropping {}", self.i) 
    }
}
```

Since `NoisyDrop` does not implement the `Copy` trait, no implicit copying will happen.

This first example illustrates what you were expecting, except now that we *are* locally allocating data, it behaves as you expect.

```rust
let s = &NoisyDrop { i: 1 };
let mut r = s;
println!("First ref is {:?}", r);
{
    let inner = &NoisyDrop { i: 2 };
    r = inner;
}
println!("Second ref is {:?}", r);
```

This will fail to compile since `NoisyDrop { i: 2 }` doesn't live as long as `r`'s type requires. However, what is actually happening in your example is more like:

```rust
let s = &NoisyDrop { i: 1 };
let t = &NoisyDrop { i: 2 };
let mut r = s;
println!("First ref is {:?}", r);
{
    let inner = t;
    r = inner;
}
println!("Second ref is {:?}", r);
```
which is unproblematic.

Not directly related to your question, but you may find the output of this final example interesting:

```rust
let s = NoisyDrop { i: 1 };
let mut r = s;
println!("First ref is {:?}", r);
{
    let inner = NoisyDrop { i: 2 };
    println!("Before assign");
    r = inner; 
    println!("After assign");
}
println!("Second ref is {:?}", r);
```

Here we're actually performing moves of the underlying data which changes who's responsible for calling `drop` on the data. This allows data to outlive the scope in which it is allocated. Note that in this example, unlike the others, `inner` is no longer usable after `r = inner;`.