Runtime speed when converting .as_bytes() or .as_ref() to be &mut

A crate on crates.io doesn't provide .as_mut() or . borrow_mut(), since such crate is published by other people, it's not comfortable or necessary to publish a modified crate. E.g, if I implement a type with only AsRef, no implementation returns mutable reference:

struct N([u8; 3]);
impl Default for N {
    fn default() -> N {
        N([0u8; 3])
    }
}
impl AsRef<[u8; 3]> for N {
    fn as_ref(&self) -> &[u8; 3] {
        &self.0
    }
}

Such situation may occur in a crate published on crates.io, it doesn't provide.as_mut() or .borrow_mut() so it's troublesome to publish a modified crate by myself. To forcibly convert the returned reference from a crate published by another person, I considered about such code:

fn main() {
    /*1st implementation*/
    let r = {
        let n = N::default();
        &mut { *n.as_ref() }
    };
    /*unsafe implementation*/
    let u = &mut unsafe { *(N::default().as_ref() as *const [u8; 3] as *mut [u8; 3]) };
}

There are safe and unsafe implementation, do they have the same runtime speed?

The first implementation doesn't do what you think it does (it copies the array out of n and gives you a mutable reference to the copy).

The second implementation is undefined behavior.

3 Likes

If not to bind N::default() to a variable, instead, just

let r = &mut { *N::default().as_ref() };

Seems correct.

If n is no longer used, can the compiler or LLVM optimize it?

You do not need to publish a crate to modify it. You can use the dependency override feature of cargo: Overriding Dependencies - The Cargo Book

The discussion Is it possible to publish crates with "path" specified? - #6 by jofas said it's impossible to publish a crate that uses path parameter, so only 2 choices: To publish a modified crate or forcibly convert a type returned by another crate.

What UB?

Oh I misread, you're copying out of the N in both contexts.

What difference do you think exists between the safe and unsafe versions? They appear to only differ on whether you're dereferencing a &[u8; 3] or *mut [u8; 3] (and there's no need for the pointer to be mutable at all there).

3 Likes

Oh, I did not infer that from your text. Do I understand your question correctly that you want to implement the equivalent of fn(&mut N) -> &mut [u8; 3] for your code?

Summarizing this thread: OP wrote this:

This doesn’t do what you want, it copies a [u8; 3] out of the &[u8; 3] returned by N::as_ref, and returns a mutable reference to that copy. The unsafe implementation does the same incorrect thing, but more verbosely[1].

There’s no correct way to do this from downstream, you’ll need to fork. Hopefully a simple PR to add the AsMut impl would get merged quickly.

Note that there are some types where an AsMut impl would be incorrect, like NonZero. There, AsRef would be fine, giving an immutable reference to the (non-zero) number. However, AsMut would allow changing the value to a zero one, shown in Rust Playground, where Some(val).is_none()!

You absolutely cannot transmute the returned pointer from as_ref into a &mut. To quote the Rustonomicon:

  • Transmuting an & to &mut is always Undefined Behavior.
  • No you can't do it.
  • No you're not special.

If you are the upstream library, you can’t have AsMut automatically inferred from AsRef. Just manually write the impl[2].

TL;DR: fork the crate and make a PR. That’s the best you can do.


  1. and more risky, because it uses unsafe ↩︎

  2. impl AsMut<FieldType> for Struct {
        fn as_mut(&mut self) -> &mut FieldType {
            &mut self.field
        }
    }
    
    ↩︎
3 Likes