Stream: t-lang/wg-unsafe-code-guidelines

Topic: Fn traits -> fn pointer

gnzlbg (Nov 02 2019 at 08:45, on Zulip):

@RalfJ @oli I was trying to answer to and came up with this code which fails under miri:

fn meaning_of_life() -> i32 { 42 }

#[repr(C)] union U<F: Fn() -> i32 + Copy> { x: F, y: fn() -> i32 }
unsafe fn a<F: Fn() -> i32 + Copy>(t: F) -> fn() -> i32 {
    U { x: t }.y

let x = unsafe { a(meaning_of_life) };
gnzlbg (Nov 02 2019 at 08:45, on Zulip):

While I see why it might fail, I'm not sure I agree that this should fail

gnzlbg (Nov 02 2019 at 08:47, on Zulip):

The actual type of F in a(meaning_of_life) is F: fn() -> i32 {meaning_of_life}

gnzlbg (Nov 02 2019 at 08:47, on Zulip):

and transmuting that to a fn() -> i32 is ok

gnzlbg (Nov 02 2019 at 08:48, on Zulip):

The error I get is:

U { x: t }.y
^^^^^^^^^^^^ Miri evaluation error: type validation failed: encountered uninitialized bytes, but expected something greater or equal to 1
note: inside call to `a::<fn() -> i32 {meaning_of_life}>`
gnzlbg (Nov 02 2019 at 08:49, on Zulip):

And I'm not sure how that is happening, since the type of F is correct, and therefore the transmute to fn()->i32 looks correct to me as well

gnzlbg (Nov 02 2019 at 08:56, on Zulip):

(and well notice that Rust segfaults, so, something weird is going on)

rkruppe (Nov 02 2019 at 08:57, on Zulip):

F is a ZST, you can't transmute that to a fn pointer, you can only cast (or coerce) it which boils down to taking the address of the function rather than mere type punning

gnzlbg (Nov 02 2019 at 08:57, on Zulip):

so that explains the uninitialized bytes

gnzlbg (Nov 02 2019 at 08:58, on Zulip):

I still find it weird that U { x: fn() -> i32 {foo}, y: fn() -> i32 } is a "useless" union

gnzlbg (Nov 02 2019 at 08:59, on Zulip):

but yeah, that can't work

gnzlbg (Nov 02 2019 at 09:00, on Zulip):

We could maybe add a Fn::as_fn(self) -> Option<fn(Args) -> Ret> method to the function traits

gnzlbg (Nov 02 2019 at 09:01, on Zulip):

that returns Some if the type can be coerced into a function pointer of the appropriate type or similar

gnzlbg (Nov 02 2019 at 09:03, on Zulip):

or somehow support as expressions for the function traits

rkruppe (Nov 02 2019 at 09:05, on Zulip):

Is there a scenario where you need to do that conversion after abstracting the concrete type to a F. Fn? And can deal with it possibly failing? In most circumstances, I imagine you could just as well do the as cast before forgetting the concrete type that can be cast to a fn pointer type.

gnzlbg (Nov 02 2019 at 09:09, on Zulip):

@rkruppe I don't know, but if you want to cast a Fn to a fn in a generic context, that cast can fail.

rkruppe (Nov 02 2019 at 09:09, on Zulip):

Obviously. So why try to do that, unless something else forces your hand?

gnzlbg (Nov 02 2019 at 09:10, on Zulip):

No idea, I've never needed this, but I'll ask the user.

gnzlbg (Nov 02 2019 at 09:12, on Zulip):

And can deal with it possibly failing?

You can actually deal with this, by creating a function that uses the Fn via some external context

gnzlbg (Nov 02 2019 at 09:13, on Zulip):

(e.g. a static, thread local, etc. but........... sounds like a bad idea anyways)

Daniel Henry-Mantilla (Nov 10 2019 at 15:06, on Zulip):

Maybe related, I think it would be useful to have a trait shared by function "items" and function items only:

trait FnItem<Args> : Copy + Sync + Frozen + Fn<Args> + 'static {
    fn call_item (args: Args) -> <Self as Fn<Args>>::Output;
    const CALL_POINTER: fn(Args) -> <Self as Fn<Args>>::Output;

And then with F : FnItem(...) -> _ one could get a guaranteed function "item" with F::call_item, and lifetime-monomorphized fn pointer with the constant.

Last update: Jul 14 2020 at 13:15UTC