Why reborrowing a slice makes its size not known in compile time?

I'm creating a mutable [u8] buffer as this:

let buffer: &mut [u8] = &mut [0u8; 34];

and using here:

repr.emit(buffer, &caps.checksum);

and reborrowing here:

assert_eq!(processor!(iface).process_ip_payload(&mut ip, &mut socket_set, Instant::from_millis(0), &mut *buffer), Ok(Some(expected_repr)));

I learned reorrowing from here: Why my slice gets moved instead of borrowed? and I sucessfully used in some codes. But this one, gives

the size for values of type [u8] cannot be known at compilation time

doesn't have a size known at compile-time

for the ...,&mut *buffer) reborrow.

The original buffer has its size known. Why reborrowing makes the size unknown?

Here are the signatures if needed:

    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
        &self,
        buffer: T,
        _checksum_caps: &ChecksumCapabilities,
    )
    fn process_ip_payload<'frame, T: AsRef<[u8]>>(
        &mut self,
        ip: &mut ip::Processor,
        sockets: &mut SocketSet,
        timestamp: Instant,
        frame: &'frame T,
    ) -> Result<Option<ip::Packet<'frame>>>

You've created a buffer with size known at compile time (&mut [0u8; 34]), and explicitly forget its size by assigning it to variable typed as &mut [u8].

The *buffer here is a dereference operation. When combined with &mut, like in &mut *, it invokes Deref::deref_mut. This turns your &mut [u8; 34] into &mut [u8], as @Hyeonu said.

I think the solution here is probably to change process_ip_payload's signature to:

    fn process_ip_payload<'frame, T: AsRef<[u8]> + ?Sized>(
        ...

By default, all generic types, T here included, are required to be Sized. Something must be Sized in order to be stored directly in a variable, so this is pretty convenient and usually makes sense.

But, as you've discovered, [u8] is not sized - it can only be held by a reference. Since process_ip_payload does take it by reference, using ?Sized lifts the restriction that T is sized and allows [u8] to take its place. In general, it's usually a good idea to use ?Sized whenever you only deal with T behind indirection (like &, &mut, Box, Arc or other smart pointers).

2 Likes

is it possible to reborrow explicitly telling the size?

Yes - simply do &mut borrow rather than &mut *borrow. The * here explicitly removes the size.

However, I doubt this is what you want. The type with the size does not implement AsRef<[u8]>.

There's a third alternative here, if you want. [u8] is not Sized, but &[u8] is. If you do &&*buffer, then you'll get an &&[u8], so T is &[u8]. &[u8] does implement AsRef<[u8]>.

why isn't it possible to reborrow as &mut [u8; 34]?

like

&mut[u8,32](*buffer)
?

also is there a problem with

let buffer: &mut [u8] = &mut [0u8; 34];?

Shouldn't it be

let mut buffer: &mut [u8] = &mut [0u8; 34];

or even

let mut buffer: &mut [u8, 34] = &mut [0u8; 34];

the strange thing is that I sucessfully used reborrowing here:

ip_repr.emit(&mut *tx_buffer, &caps.checksum);

where tx_buffer is of type &mut [u8]

where emit has signature

pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
    &self,
    buffer: T,
    _checksum_caps: &ChecksumCapabilities,
)

why size wasn't required here?

It wasn't required because you didn't take [u8], you took in &mut [u8]. The critical thing is that &mut [u8] is a separate type from [u8]. The &mut here is part of the type, so T is literally &mut [u8].

In process_ip_payload, you take in &T. &mut [u8] is coerced/downgraded into &[u8]. Then since the parameter is &T, and the data it gets is &[u8], T is set to [u8].

T: Sized is required in both. It's just that &mut [u8] is Sized, while [u8] isn't.


There's a syntax difference here.

let mut buffer = ... means "let me change the value of buffer".

let buffer: &mut ... = ... means "let me change whatever the buffer points to".

If you did let mut buffer: &mut ... = ..., it would mean that you can both change what buffer points to and change the value at the thing it points to.

If you wanted to make buffer point to a different &mut [u8] value, then it needs to be declared let mut buffer. But if you are satisfied with changing the values in the [u8], and you don't need to point to a different slice, then let buffer = &mut ... works.

&[T] slices are dynamically sized. [u8; 34] is not a slice, it is an array, and it has a static size (34 bytes). &[u8] is a dynamically sized slice. &[u8; 34] is not a slice, it is a shared reference to an array with a static size.

If you want to transform a slice into an array reference, you can use some unsafe code, or a crate like arrayref which hides those details for you. You probably don't actually want to do this in general, but the option is on the table if you have a very strong case for it.

I missed this.

&mut buffer literally gives you &mut [u8; 34]. This is a reborrow. It is possible.

1 Like

Nice explanation!

So we have two cases:

&mut [u8] to T, of course T is the entire thing, which is &mut [u8]

&mut[8] is coerced into [u8], why? Then &[u8] to &T, doing pattern matching with the eye we see that T can only be [u8].

So I get, [u8] does not implement Sized, that's the main problem.

ok, so I did another thing by looking at a similar code

let buffer = &mut vec![0u8; 34];
repr.emit(buffer.as_mut_slice(), &caps.checksum);
assert_eq!(processor!(iface).process_ip_payload(&mut ip, &mut socket_set, Instant::from_millis(0), buffer),
                   Ok(Some(expected_repr)));

and it works.

But I have a doubt about the coercion in this case.

&mut vec![0u8; 34]; is matched against &T. Doing eye pattern matching T would have to be mut vec![_;34]. But there's no such thing as a mut T, only &mut T. So I guess T ends up being vec![0u8;34]. Well, vec is Sized and implements AsRef, so that's why it works here.

What you think?

Looks like a good analysis! I agree with what you've said.

Responding to your individual question:

Ah, I might have misspoken. &mut [u8] is coerced to &[u8], not to [u8]. This is a general rule: rust downgrades &mut Xxxx into &Xxxx if it can't get &mut Xxxx to work otherwise. Since the pattern &T always starts with &, there's no way an &mut [u8] would fit. So it downgrades &mut [u8] to &[u8], and finds a pattern there.

This is accurate!

I would change it slightly, just what you call the types. vec![0u8; 34] produces a value with type Vec<u8>. Then &mut vec![0u8; 34] produces &mut Vec<u8>.

Similar to with the other one, &mut Anything won't fit &T, so Rust downgrades &mut Vec<u8> to &Vec<u8>. Then &Vec<u8> is matched against &T, and as you say, you get T = Vec<u8>.

Vec<u8> implements Sized and AsRef, so it works, like you say.

My words changed vec![0u8; 34] to Vec<u8>, but the content is the same!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.