Can `get_ref()` and `into_inner()` be used on a pinned wrapper value?

I recently implemented a struct that wraps a BufRead and, in the spirit of the various wrappers I've seen in the standard library, provides get_ref(), get_mut(), and into_inner() methods for accessing the wrapped value, including a couple tests that make use of these methods non-trivially (i.e., the return values are operated on rather than just type-checked and thrown away). Now I'm implementing an async equivalent that wraps AsyncBufRead, and in my tests (which involve pinning the wrapper before doing anything with it), my attempts to use get_mut() and into_inner() fail because things are either moved or can't be moved.

Here's a very minimal example of one of the problems I'm having:

use std::pin::Pin;

struct Container<T> {
    inner: T
}

impl<T> Container<T> {
    fn new(inner: T) -> Self {
        Container {inner}
    }
    
    fn into_inner(self) -> T {
        self.inner
    }
}

fn main() {
    let s = "Foo".to_string();
    let cntnr = Container::new(s);
    tokio::pin!(cntnr);
    // Imagine I'm doing stuff on this line that requires cntnr to be pinned.
    let s2 = Pin::into_inner(cntnr).into_inner();
    // This doesn't work either:
    //let s2 = cntnr.into_inner();
    println!("{s2:?}");
}

[Playground link]

Compiler output:

error[E0507]: cannot move out of a mutable reference
  --> src/main.rs:22:14
   |
22 |     let s2 = Pin::into_inner(cntnr).into_inner();
   |              ^^^^^^^^^^^^^^^^^^^^^^^------------
   |              |                      |
   |              |                      value moved due to this method call
   |              move occurs because value has type `Container<String>`, which does not implement the `Copy` trait
   |
note: this function takes ownership of the receiver `self`, which moves value
  --> src/main.rs:12:19
   |
12 |     fn into_inner(self) -> T {
   |                   ^^^^

For more information about this error, try `rustc --explain E0507`.

Hence, my question: Is there a way to call a type's get_ref(), get_mut(), and/or into_inner() on a value that's been pinned? Or are these methods only usable before pinning?

My understanding is that you want/need a Container whose contents are pinned if the container is pinned (“structural pinning”). If that's so, then the only safe method that you are allowed to provide is one with the signature fn(Pin<&mut Container<T>> -> Pin<&mut T>). Anything less restrictive, such as -> &mut T or -> T will make it possible for a caller to move the T before it is dropped, which breaks the Pin contract.

On the other hand, if you do not intend for the T to be pinned, only other parts of the Container<T>, then you can provide fn(Pin<&mut Container<T>> -> &mut T). You have to pick one or the other — not both.

These kinds of functions/methods, that take (a reference to) a thing and return (a reference to) a specific one of its parts, are called projections, and the pin_project library can help you write projections of both kinds without needing to write your own unsafe code. (Getting access to the contents of a Pin<&mut T> when T: !Unpin always involves unsafe code at some level.)

You may also want to read the section of the std::pin documentation on projections — it goes into more detail on the implications of making one or the other choice. (If you stick to pin_project's macro generated projections, you don't have to worry about doing something unsound, but you also might be surprised by what you can't do.)

1 Like

That's not really what I'm asking about. For a more specific example, let's look at tokio::io::BufReader<R>. This has get_ref(), get_mut(), and into_inner() methods for accessing the wrapped reader, along with a get_pin_mut() method with the signature (self: Pin<&mut Self>) -> Pin<&mut R>. Clearly, the last method can only be called when the BufReader is pinned, but when can the first three methods be called relative to pinning? Is there a way to call them on a pinned BufReader, or are they only meant for use prior to/in the absence of pinning? I need to know in order to figure out a decent and correct way to test analogous methods in my code.

You can't use those methods with pinning, because

  • BufReader<R>::into_inner takes BufReader<R> which you can't get from Pin<BufReader<R>>.
  • BufReader<R>::get_mut takes &mut BufReader<R> which you can't get from Pin<&mut BufReader<R>>.

…unless R implements Unpin, in which case so does BufReader<R>, and that enables Deref and Pin::into_inner() on the Pin and thus its contents. In general, Unpin makes it possible to do “normal” things with a Pinned reference, for the situations when pinning is not necessary. When pinning is necessary, only pin-projection (BufReader::get_pin_mut()) is available.

3 Likes

Could you elaborate on this part? In the code I posted in the initial comment, Container<String> should be Unpin as I understand it, yet calling Pin::into_inner() on it doesn't work.

After running tokio::pin!(cntnr), the variable named by cntnr is of type Pin<&mut Container<String>>. Calling Pin::into_inner() on that gives you an &mut Container<String>, which you cannot move a Container<String> out of due to the usual characteristics of &mut.

This isn't a feature of Pin but of tokio::pin! — the way it provides pinning is by putting the actual value in local storage you can't ever refer to, and leaving you with Pin<&mut ...> of it. There's no way to recover the original value from that.

If you want an owned pinned value that you can unpin, you probably need to use Pin<Box> instead. But I'm not an expert on practical uses of pinning and there may be another way.

1 Like

In general, once you've pinned a !Unpin value, you can only call self: Pin<&mut Self> and &self methods on it.

(But you can call the other methods if you haven't pinned it yet.)

2 Likes