Skip to content

Cannot derive BorshDeserialize for a struct containing Box<[T]> #323

@SmnTin

Description

@SmnTin

The implementation of BorshDeserialize for Box<T> has the following bounds, which I find quite strange:

impl<T, U> BorshDeserialize for Box<T>
where
    U: Into<Box<T>> + Borrow<T>,
    T: ToOwned<Owned = U> + ?Sized,
    T::Owned: BorshDeserialize,
{
    // ...
}

I guess this has been done with the intention to reuse the impl of BorshDeserialize for String when deserializing Box<str>.

Unfortunately, for Box<[T]>, these bounds require [T]: ToOwned and, thus, T: Clone.

First, this differs from the impl for Vec<T>, which only requires T: BorshDeserialize without the additional bound of T: Clone. Second, this has a nasty interaction with deriving and generic parameters.

So, while the following code compiles:

#[derive(BorshDeserialize)]
pub struct Items<T> {
    pub items: Vec<T>,
}

The version with Box<[T]> fails to compile:

#[derive(BorshDeserialize)]
pub struct Items<T> {
    pub items: Box<[T]>,
}

...with the following error:

error[E0277]: the trait bound `[T]: ToOwned` is not satisfied
   --> borsh-bug/src/lib.rs:3:10
    |
  3 | #[derive(BorshDeserialize)]
    |          ^^^^^^^^^^^^^^^^ the trait `ToOwned` is not implemented for `[T]`, which is required by `Box<[T]>: BorshDeserialize`
    |
    = note: required for `Box<[T]>` to implement `BorshDeserialize`
    = note: this error originates in the derive macro `BorshDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info)

Semantically, however, deserializing Box<[T]> is the same as Vec<T>. Therefore, there should be no differences in bounds.

I believe it would have been much better if there were just separate implementations for sized and unsized types:

impl<T: BorshDeserialize> BorshDeserialize for Box<T> { ... }
impl<T: BorshDeserialize> BorshDeserialize for Box<[T]> { ... }
impl BorshDeserialize for Box<str> { ... }
impl BorshDeserialize for Box<CStr> { ... }
impl BorshDeserialize for Box<OsStr> { ... }

But this will be a breaking change if it is changed to this now. Maybe it can at least be special cased in borsh-derive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions