Stream: general

Topic: &Box can't be &dyn Error


Jake Goulding (Jan 28 2019 at 01:17, on Zulip):

I'm missing something obvious here. Why can't a &Box<dyn Error> be treated as &dyn Error?

use std::{error, fmt};

#[derive(Debug)]
struct Error(Box<dyn error::Error + 'static>);

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        "dummy".fmt(f)
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        Some(&self.0)
    }
}
error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
  --> src/lib.rs:14:14
   |
14 |         Some(&self.0)
   |              ^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::error::Error + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<(dyn std::error::Error + 'static)>`
   = note: required for the cast to the object type `dyn std::error::Error`
Jake Goulding (Jan 28 2019 at 01:18, on Zulip):

I can get it to work by doing Some(&*self.0), but that doesn't generalize to cases where my error isn't boxed (e.g std::io::Error)

Jake Goulding (Jan 28 2019 at 01:51, on Zulip):

Here's the slightly broader case where I'd like the match arms to be source-identical:

use std::{error, fmt};

#[derive(Debug)]
enum Error {
    A(Box<dyn error::Error + 'static>),
    B(std::io::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        "dummy".fmt(f)
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Error::A(x) => Some(x),
            Error::B(x) => Some(x),
        }
    }
}
simulacrum (Jan 28 2019 at 04:09, on Zulip):

@Jake Goulding The first one compiles with &*self.0

simulacrum (Jan 28 2019 at 04:10, on Zulip):

I suspect the problem you're running into is that when the compiler attempts to unsize Box<dyn Error> into dyn Error (in order to get &dyn Error), that fails, as you can't unsize an unsized type

simulacrum (Jan 28 2019 at 04:10, on Zulip):

but if we deref the box first, then we have dyn Error and we can & that to then return it as &dyn Error

simulacrum (Jan 28 2019 at 04:12, on Zulip):

For the second we want Error::A(x) => Some(&**x), because match bindings make x have &Box<dyn Error> type so the first * gets rid of the reference, and the second removes the Box

Jake Goulding (Jan 28 2019 at 14:14, on Zulip):

Friends over in SO chat pointed out I can call Borrow::borrow on both to get it to work:

Error::A(x) => Some(x.borrow()),
Error::B(x) => Some(x.borrow()),
Jake Goulding (Jan 28 2019 at 14:16, on Zulip):

I suspect the problem you're running into is that when the compiler attempts to unsize Box<dyn Error> into dyn Error (in order to get &dyn Error), that fails, as you can't unsize an unsized type

Maybe this is what feels weird; by sprinkling some * in, it works. Thus it feels like the compiler should be able to do the same. I know that's not always the case...

simulacrum (Jan 28 2019 at 16:02, on Zulip):

Unfortunately in this case the compiler wants to strictly auto-coerce and that doesn't involve deref coercions

simulacrum (Jan 28 2019 at 16:02, on Zulip):

the borrow call is essentially the same as &** in my suggestion (invokes implicit auto-deref)

Jake Goulding (Jan 28 2019 at 16:40, on Zulip):

except that it's "generic" enough to handle &Box<dyn Error> and &io::Error

simulacrum (Jan 28 2019 at 20:52, on Zulip):

well, Borrow itself isn't but the method coercion is more lax than the unsizing coercion

simulacrum (Jan 28 2019 at 20:52, on Zulip):

because it'll insert an arbitrary amount of *

mikeyhew (Jan 31 2019 at 17:36, on Zulip):

Does Box<dyn Error> implement Error? If it does, then shouldn't you be able to unsize it to dyn Error itself?

Jake Goulding (Jan 31 2019 at 19:37, on Zulip):

@mikeyhew I think that's my intuition as well. There's an impl<T: Error> Error for Box<T>, but not for Box<dyn Error>:

use std::error::Error;

fn is<E: Error + ?Sized>() {}

fn main() {
    is::<Box<Error>>();
}
error[E0277]: the size for values of type `dyn std::error::Error` cannot be known at compilation time
 --> src/main.rs:6:5
  |
6 |     is::<Box<Error>>();
  |     ^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `dyn std::error::Error`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<dyn std::error::Error>`
note: required by `is`
 --> src/main.rs:3:1
  |
3 | fn is<E: Error + ?Sized>() {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
mikeyhew (Jan 31 2019 at 19:41, on Zulip):

Oh I see, it makes sense then that the error message is about dyn Error not being Sized

mikeyhew (Jan 31 2019 at 19:48, on Zulip):

That's weird though, why doesn't the impl of Error for Box<T> use T: ?Sized? Isn't that the whole point of impls on Box? To make Box<dyn Trait> implement Trait?

Jake Goulding (Feb 01 2019 at 00:56, on Zulip):

that does seem odd...

simulacrum (Feb 01 2019 at 02:48, on Zulip):

You cannot unsize things that are already unsized -- if something currently takes up two words then the compiler currently doesn't support any sort of double-unsizing

simulacrum (Feb 01 2019 at 02:48, on Zulip):

two words being a fat ptr, to be exact

simulacrum (Feb 01 2019 at 02:48, on Zulip):

I'm not sure if this is an underlying limitation though

mikeyhew (Feb 01 2019 at 17:21, on Zulip):

@simulacrum I don't think you got my meaning. This is what I meant:

trait Trait {}
impl Trait for i32 {}
impl<T: ?Sized> Trait for Box<T> {}

fn main() {
    let bx = Box::new(5) as Box<dyn Trait>;
    let r = &bx as &dyn Trait;
}
mikeyhew (Feb 01 2019 at 17:23, on Zulip):

Also, unsizing a Box<dyn Trait> to a Box<dyn Trait> does work, it's just a no-op

mikeyhew (Feb 01 2019 at 17:29, on Zulip):

Not sure if I'm being very clear. The code example I just posted is something that compiles and runs just fine. It works because Box<dyn Trait> is a Sized type that implements Trait, and therefore can itself be unsized to dyn Trait. &Box<dyn Error> -> &dyn Error would work too, except Box<dyn Error> doesn't implement Error (and it seems like it should).

mikeyhew (Feb 01 2019 at 17:36, on Zulip):

Now, I doubt you would really want to do this, if you can avoid it. Unsizing &Box<dyn Trait> to &dyn Trait adds an extra layer of indirection that you can avoid just by doing &*bx, which is what @simulacrum was suggesting @Jake Goulding should do instead.

mikeyhew (Feb 01 2019 at 17:39, on Zulip):

"you can't unsize an unsized type" is true in other cases - for example, you can't turn an &str into an &dyn Trait. But it doesn't apply here.

RalfJ (Feb 01 2019 at 17:41, on Zulip):

except Box<dyn Error> doesn't implement Error (and it seems like it should).

and that's likely just because a T: ?Sized was forgotten when writing that impl, right?

simulacrum (Feb 01 2019 at 20:31, on Zulip):

hm, yeah, probably?

Jake Goulding (Feb 03 2019 at 01:16, on Zulip):

Not the error I expected:

error[E0119]: conflicting implementations of trait `core::convert::From<alloc_crate::boxed::Box<dyn error::Error>>` for type `alloc_crate::boxed::Box<dyn error::Error>`:
   --> src/libstd/error.rs:207:1
    |
207 | impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: conflicting implementation in crate `core`:
            - impl<T> core::convert::From<T> for T;

error[E0119]: conflicting implementations of trait `core::convert::From<alloc_crate::boxed::Box<dyn error::Error + core::marker::Send + core::marker::Sync>>` for type `alloc_crate::boxed::Box<dyn error::Error + core::marker::Send + core::marker::Sync>`:
   --> src/libstd/error.rs:243:1
    |
243 | impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: conflicting implementation in crate `core`:
            - impl<T> core::convert::From<T> for T;
Last update: Nov 20 2019 at 12:15UTC