Skip to content

Prefer *_chunk APIs for extracting arrays from slices #16396

@jrose-signal

Description

@jrose-signal

What it does

The following slice methods extract arrays from longer slices:

  • first_chunk and split_first_chunk (1.77, const 1.77)
  • first_chunk_mut and split_first_chunk_mut (1.77, const 1.83)
  • last_chunk (1.77, const 1.80) and split_last_chunk (1.77, const 1.77)
  • last_chunk_mut and split_last_chunk_mut (1.77, const 1.83)

They're not always applicable, but if existing code is slicing or calling split_at[_*] with a constant index and then immediately turning the result into an array via TryInto, using one of the *_chunk APIs can be clearer about an array being involved. (Being careful about whether the array is a reference or a value, of course!)

Advantage

  • Avoids using TryInto for a conversion that is infallible by construction.
  • In many cases the *_chunk APIs allow omitting the array length entirely; it'll be inferrable from context. (As with all type inference, this could also be considered a disadvantage.)

Drawbacks

  • There's no implicitly-panicking version of the *_chunk APIs, so even though there's no need to unwrap TryInto anymore, some of the transformations would introduce an explicit unwrap for the split point being in bounds. This can make the resulting code longer than the original code.

Example

// Before
u32::from_be_bytes(bytes[..4].try_into().unwrap())
// After
u32::from_be_bytes(*bytes.first_chunk().expect("slice is long enough"))
// Before
u32::from_be_bytes(bytes.get(..4)?.try_into().unwrap())
// After
u32::from_be_bytes(*bytes.first_chunk()?)
// Before
u32::from_be_bytes(bytes.get(bytes.len() - 4..)?.try_into().unwrap())
// After
u32::from_be_bytes(*bytes.last_chunk()?)
// Before
let (magic_number, contents) = bytes.split_at_checked(8)?;
check(magic_number.try_into().unwrap());
// After
let (magic_number, contents) = bytes.split_first_chunk::<8>()?;
check(magic_number);
// Before
let (contents, auth_tag) = bytes.split_at_mut(bytes.len() - 16);
cipher.write_auth_tag(auth_tag.try_into().unwrap());
// After
let (contents, auth_tag) = bytes.split_last_chunk_mut::<16>().expect("slice is long enough");
cipher.write_auth_tag(auth_tag);

A possible extension to also cover copy_from_slice when copying from an array, for clearer code and an avoided (optimizable) bounds check:

// Before
let (magic_number, contents) = bytes.split_at_mut(8);
magic_number.copy_from_slice(&MAGIC_U32.to_be_bytes());
// After
let (magic_number, contents) = bytes.split_first_chunk_mut::<8>().expect("slice is long enough");
*magic_number = MAGIC_U32.to_be_bytes();

Comparison with existing lints

No response

Additional Context

This operation is specifically interesting for pulling arrays out of slices. If someone wants to pull arrays out of arrays, the APIs in rust-lang/rust#90091 may be a better choice…but the ideal interface for those is blocked on stronger generic const exprs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintArea: New lints

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions