Mapping a Vec of Boxed ToSql to ref ToSql

I'd like to map a Vec of boxes to a Vec of refs for the ToSql trait from tokio postgres.

Vec<Box<dyn ToSql>> -> Vec<& dyn ToSql>

I'm having a lot of trouble with this.

I've tried this

pub fn ref_from_box_vec<'a>(x: Vec<Box<dyn ToSql>>) -> Vec<&'a dyn ToSql> {
    x.into_iter().map(|y| *y).collect()
}

but get several compile errors

error[E0277]: the size for values of type `dyn ToSql` cannot be known at compilation time
   --> edb-core/src/postgres_common/core.rs:375:19
    |
375 |     x.into_iter().map(|y| *y).collect()
    |                   ^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `dyn ToSql`
note: required by a bound in `std::iter::Iterator::map`
   --> /home/decapo01/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:776:12
    |
776 |     fn map<B, F>(self, f: F) -> Map<Self, F>
    |            ^ required by this bound in `std::iter::Iterator::map`

error[E0277]: the size for values of type `dyn ToSql` cannot be known at compilation time
   --> edb-core/src/postgres_common/core.rs:375:27
    |
375 |     x.into_iter().map(|y| *y).collect()
    |                           ^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `dyn ToSql`
    = note: the return type of a function must have a statically known size

error[E0599]: the method `collect` exists for struct `std::iter::Map<std::vec::IntoIter<Box<dyn ToSql>>, [closure@edb-core/src/postgres_common/core.rs:375:23: 375:26]>`, but its trait bounds were not satisfied
   --> edb-core/src/postgres_common/core.rs:375:31
    |
375 |     x.into_iter().map(|y| *y).collect()
    |                               ^^^^^^^ method cannot be called on `std::iter::Map<std::vec::IntoIter<Box<dyn ToSql>>, [closure@edb-core/src/postgres_common/core.rs:375:23: 375:26]>` due to unsatisfied trait bounds

I've also tried this

pub fn ref_from_box_vec<'a>(x: Vec<Box<dyn ToSql>>) -> Vec<&'a dyn ToSql> {
    x.iter().map(|y| *y.to_owned()).collect()
}

and get this error

error[E0277]: a value of type `Vec<&dyn ToSql>` cannot be built from an iterator over elements of type `Box<dyn ToSql>`
    --> edb-core/src/postgres_common/core.rs:379:5
     |
379  |     x.iter().map(|y| *y.to_owned()).collect()
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------- required by a bound introduced by this call
     |     |
     |     value of type `Vec<&dyn ToSql>` cannot be built from `std::iter::Iterator<Item=Box<dyn ToSql>>`
     |
     = help: the trait `FromIterator<Box<dyn ToSql>>` is not implemented for `Vec<&dyn ToSql>`
     = help: the trait `FromIterator<T>` is implemented for `Vec<T>`

You can't return a reference from a box because someone needs to own the value, just as you can't define a function (value: T) -> &T. Iterator::map requires its type parameter to be Sized, but that doesn't really matter because what you're trying to do is just not possible in Rust.

Note that your 'a is not tied to any input, so the caller could ask for any lifetime they want, even 'static. That's bad news for borrow checking, because you're not actually borrowing from anything.

If this is really the signature you want, you can leak them, .map(|y| &*Box::leak(y)), but that is a true memory leak -- those heap objects will not be freed when the reference is finally done.

It would be better if you can borrow from the original Vec, which is normally done through a slice like:

pub fn ref_from_box_vec<'a>(x: &'a [Box<dyn ToSql>]) -> Vec<&'a dyn ToSql> {
    x.iter().map(|y| &**y).collect()
}

In that case, you can also omit the explicit lifetime to leave it implied:

pub fn ref_from_box_vec(x: &[Box<dyn ToSql>]) -> Vec<&dyn ToSql>

A reference to an unsized type is still Sized itself -- &dyn Trait is the size of two pointers. So it's not inherently a problem for map to deal with trait objects in this way, as long as you deal with the lifetime and ownership question.

4 Likes