hyperium / http

Rust HTTP types
Apache License 2.0
1.12k stars 283 forks source link

Why need to wrap with Option before downcast? #580

Closed npuichigo closed 1 year ago

npuichigo commented 1 year ago

https://github.com/hyperium/http/blob/34a9d6bdab027948d6dea3b36d994f9cbaf96f75/src/convert.rs#L4-L6

Can anyone help to explain why we need to wrap the value into Option before downcast instead of down casting directly?

tesaguri commented 1 year ago

Disclaimer: I'm not the author of that code.

I guess by "down casting directly," you mean something like fn($in_ty) -> Option<$out_ty>, but the downcasting methods provided by the standard library are only implemented for the trait object type dyn Any, which (in current stable Rust) you can only use through a reference like Box<dyn Any> or &mut Any. Creating a Box<dyn Any> incurs an allocation cost so fn downcast_mut<T>(&mut dyn Any) -> Option<&mut T> is the preferable method here.

Naively, one might attempt to get a &mut $out_ty with that method, but you cannot simply take an owned $out_ty value out of the borrowed reference. If you could provide a placeholder value like <$out_ty>::default(), you could mem::swap the referent with the placeholder to get an owned value, but $out_ty is an arbitrary type which won't even let you get such a placeholder.

Here is where the Option trick plays the role. downcast_mut::<Option<$out_ty>>() gives you an &mut Option<$out_ty>, on which you can just call take() to get the owned $out_ty value, which is equivalent to providing the None value as a placeholder.

One could still think of an alternative way that involves transmute, but that would be unsafe and I'm not sure doing that would even be sound.

eagr commented 1 year ago

I guess the goal of the macro is to replace $val with a downcast value to make $body compile (which contains the name $val). Without the Option trick, other alternatives would be something like

let $val = *(Box::new($val) as Box<dyn std::any::Any>).downcast().unwrap();
$body

with extra allocations

if let Some(ref_val) = (&$val as &dyn std::any::Any)
    .downcast_ref::<$out_ty>()
{

    let $val = ref_val.clone();
    $body
}

with extra copies

npuichigo commented 1 year ago

Thanks for your guys' detailed answers!