Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it possible to return a mutable reference to a literal from a function?

The current edition of The Rustonomicon has this example code:

use std::mem;

pub struct IterMut<'a, T: 'a>(&'a mut [T]);

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        let slice = mem::replace(&mut self.0, &mut []);
        if slice.is_empty() {
            return None;
        }

        let (l, r) = slice.split_at_mut(1);
        self.0 = r;
        l.get_mut(0)
    }
}

I'm confused about this line in particular:

let slice = mem::replace(&mut self.0, &mut []);
//                                    ^^^^^^^ 

How does this borrow check? If this were an immutable borrow, RFC 1414 indicates that the [] rvalue should have 'static lifetime, so that an immutable borrow would borrow-check, but the example shows a mutable borrow! It seems that one of two things must be going on:

  • Either [] is a temporary (so that it can be used mutably), in which case it would not have 'static lifetime, and should not borrow-check;
  • Or that [] has 'static lifetime, and therefore it should not be possible to take a mutable borrow (since we don't guarantee exclusive access as we take the borrow), and should not borrow-check.

What am I missing?

Related:

  • Why can I return a reference to a local literal but not a variable?

    This question focuses on immutable references; this question is about mutable references.

  • Why is it legal to borrow a temporary?

    This question focuses on taking references inside of a function; this question is about returning a reference.

like image 895
Aidan Cully Avatar asked May 09 '19 19:05

Aidan Cully


People also ask

What is a mutable reference?

A mutable reference is a borrow to any type mut T , allowing mutation of T through that reference. The below code illustrates the example of a mutable variable and then mutating its value through a mutable reference ref_i . fn main() {

How do you borrow arguments in Rust?

An Example of Borrowing in Rust You can borrow the ownership of a variable by referencing the owner using the ampersand (&) symbol. Without borrowing by referencing, the program would panic. It would violate the ownership rule that a value can have one owner, and two variables cannot point to the same memory location.


1 Answers

TL;DR: empty arrays are special cased in the compiler and it's safe because you can't ever dereference the pointer of a zero-length array, so there's no possible mutable aliasing.


RFC 1414, rvalue static promotion, discusses the mechanism by which values are promoted to static values. It has a section about possible extensions for mutable references (bolding mine):

It would be possible to extend support to &'static mut references, as long as there is the additional constraint that the referenced type is zero sized.

This again has precedence in the array reference constructor:

// valid code today
let y: &'static mut [u8] = &mut [];

The rules would be similar:

  • If a mutable reference to a constexpr rvalue is taken. (&mut <constexpr>)
  • And the constexpr does not contain a UnsafeCell { ... } constructor.
  • And the constexpr does not contain a const fn call returning a type containing a UnsafeCell.
  • And the type of the rvalue is zero-sized.
  • Then instead of translating the value into a stack slot, translate it into a static memory location and give the resulting reference a 'static lifetime.

The zero-sized restriction is there because aliasing mutable references are only safe for zero sized types (since you never dereference the pointer for them).

From this, we can tell that mutable references to empty arrays are currently special-cased in the compiler. In Rust 1.39, the discussed extension has not been implemented:

struct Zero;

fn example() -> &'static mut Zero {
    &mut Zero
}
error[E0515]: cannot return reference to temporary value
 --> src/lib.rs:4:5
  |
4 |     &mut Zero
  |     ^^^^^----
  |     |    |
  |     |    temporary value created here
  |     returns a reference to data owned by the current function

While the array version does work:

fn example() -> &'static mut [i32] {
    &mut []
}

See also:

  • Why is it legal to borrow a temporary?
  • Why can I return a reference to a local literal but not a variable?
like image 101
Shepmaster Avatar answered Sep 21 '22 10:09

Shepmaster