edwardw
edwardw

Reputation: 13942

In order to downcast a trait object why does it have to be static?

The std::error::Error trait is transiting to a new design. Noticeably its cause method:

fn cause(&self) -> Option<&dyn Error>

is deprecated and to be replaced by:

fn source(&self) -> Option<&(dyn Error + 'static)>

Besides the name, the only change is that the new method returns 'static. The reason given by fix error RFC is:

The problem with the existing cause API is that the error it returns is not 'static. This means it is not possible to downcast the error trait object, because downcasting can only be done on 'static trait objects (for soundness reasons).

This is not obvious to me at all. Why is that?

Upvotes: 5

Views: 899

Answers (1)

edwardw
edwardw

Reputation: 13942

The best way to understand it is to implement one! This SO question:

is a good starting point. The implementation in it relies on std::any::Any but doesn't shed light on why 'static is required. If it is to be implemented directly in the same vein as (dyn Any)::downcast_ref() though (playground):

use std::any::TypeId;

trait A {
    fn type_id(&self) -> TypeId
    where
        Self: 'static,
    {
        TypeId::of::<Self>()
    }
}

impl dyn A + 'static {
    fn is<T: A + 'static>(&self) -> bool {
        let t = TypeId::of::<T>();
        let concrete = self.type_id();

        t == concrete
    }

    fn downcast_ref<T: A + 'static>(&self) -> Option<&T> {
        if self.is::<T>() {
            unsafe { Some(&*(self as *const dyn A as *const T)) }
        } else {
            None
        }
    }
}

struct B;

impl A for B {}

fn main() {
    let a: Box<dyn A> = Box::new(B);

    let _b: &B = match a.downcast_ref::<B>() {
        Some(b) => b,
        None => panic!("&a isn't a B!"),
    };
}

The unsafe here is actually safe because we are only downcasting back to the original type thanks to the type id check. The implementation pivots on TypeId to give us that guarantee:

A TypeId is currently only available for types which ascribe to 'static, but this limitation may be removed in the future.

If digging a little bit deeper, the way how this type id is calculated at the moment finally gives us the answer. That is, a struct Struct<'a> may have N instantiations with different concrete substitutions for 'a, but the type id is the same. Were the TypeId to be available to all Struct<'a>, we then can downcast any Struct<'a> to Struct<'static>, which would be a soundness issue.

Upvotes: 4

Related Questions