View types not implementable as Deref – best practices?

In one of my projects, I’m defining a struct that holds two vectors and want to define a view type over slices of each of the members, in the way that &[T] is a view over Vec<T> or &str is a view over String. Since the view type must contain two slice references, it can’t be expressed as a reference itself (so I can’t have &Thing borrowing ThingBuf, unless I’m missing something).

I want to have both immutable and mutable views over the resource (i.e. separate ThingRef and ThingMut types instead of &Thing and &mut Thing), and some of the methods I’m implementing return references (mutable in some cases) inside the resource being borrowed.

What are the best practices for implementing such types so that they can be used like built-in references as much as possible? Note that I’m using nightly Rust.

Edit: here’s a contrived example that shows what I’m trying to do.

I think your issue lines in this line:

impl<'a> TwoSliceMut<'a> {
    pub fn split_first_mut(&'a mut self) -> Option<(&mut i32, &mut i32, TwoSliceMut<'a>)> { ... }

You could change the &mut i32 return values to &'a mut i32 but that'd mean calling split_first_mut() "locks" the TwoSliceMut object so you will never be able to use it again. Another user on this forum worded it better, but &'a mut self is almost never what you want.

What I would do is tie the lifetime of all the return values to the &mut self reference instead of 'a.

impl<'a> TwoSliceMut<'a> {
  pub fn split_first_mut(&mut self) -> Option<(&mut i32, &mut i32, TwoSliceMut<'_>)> { ... }

It makes your example compile, but that may or may not be an acceptable solution for your original problem.


Maybe you can do an iterator that produces a tuple view pairwise traveling down each elements. Quindot taught me a trick of keeping ownership of the iterators by:

if let Some(val) = { do something with val }

Depend on your use-case it would be two shared references or two mutable references.

In general,

  • With shared references, you can copy them out, even from behind a shorter borrow
  • With mutable references, you're going to be limited by the length of the outer borrow
    • If you borrow &'a mut MyStruct<'a>, MyStruct<'a> is mutably borrowed for the rest of its lifetime, which may compile, but is [1] never what you want
    • This is the root of the error in your playground

Applying that to your playground, I arrived here. Every split_* method had its signature tweaked.

If you're looking at the slice ([T]) split_first_mut method, you may be wondering how they can avoid shrinking the lifetime (when T is a &mut U, say). A big difference is that the slice method takes a &mut [T], while you're taking a &mut { &mut [T], &mut [T] } -- you're dealing with nested references, and they are not.

You could make something more like the slice method if you took ownership:

    pub fn slice_like_split_first_mut(self) -> Option<(&'a mut i32, &'a mut i32, TwoSliceMut<'a>)> {
        match self {
            TwoSliceMut { a: [a1, a @ ..], b: [b1, b @ ..] } => {
                Some((a1, b1, TwoSliceMut { a, b }))
            _ => None,

However, because it takes ownership, you can no longer use the view after calling this method. The same is true of the &mut [T] "view" -- only it probably seems like you can, because of automatic reborrowing. That doesn't apply recursively, but you could make a reborrow method...

    pub fn reborrow(&mut self) -> TwoSliceMut<'_> {

...however then you're pretty much back to this...

    pub fn split_first_mut(&mut self) -> Option<(&mut i32, &mut i32, TwoSliceMut<'_>)> {

...except it's less ergonomic:

if let Some((a, _, _)) = mut_view.reborrow().slice_like_split_first_mut() {

That being said, slice_like_split_first_mut might still have some use depending on what you're doing (e.g. iteration).

  1. practically speaking ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.