Why does this closure require a type annotation?

I wrote a macro to avoid repeating boilerplate for a bunch of related From impls:

struct CStrV {
   v: *const *const u8
}
impl CStrV {
   fn new(v: *const *const u8) -> Self { Self { v } }
   // etc
}
macro_rules! cstrv_from {
    ($ty:ty) => {
        impl From<$ty> for CStrV {
            fn from(v: $ty) -> CStrV {
                CStrV::new(v)
            }
        }
    };
    ($ty:ty, $conv:expr) => {
        impl From<$ty> for CStrV {
            fn from(v: $ty) -> CStrV {
                CStrV::new($conv(v))
            }
        }
    };
}

cstrv_from! { *const *const u8 }
cstrv_from! { *const *mut u8,  |v| v.cast::<*const u8>() }
cstrv_from! { *mut *const u8,  |v| v.cast_const() }
cstrv_from! { *mut *mut u8,    |v| v.cast::<*const u8>().cast_const() }

This doesn't compile, demanding type annotations for the conversion closures:

error[E0282]: type annotations needed
  --> src/lib.rs:26:33
   |
26 | cstrv_from! { *const *mut u8,  |v| v.cast::<*const u8>() }
   |                                 ^  - type must be known at this point
   |
help: consider giving this closure parameter an explicit type
   |
26 | cstrv_from! { *const *mut u8,  |v: /* Type */| v.cast::<*const u8>() }
   |                                  ++++++++++++

I thought it was the macro's fault, but I get the same error if I manually expand the macro, e.g.

impl From<*mut *const u8> for CStrV {
    fn from(v: *mut *const u8) -> CStrV {
        CStrV::new((|w| w.cast_const())(v))
    }
}

The required argument type should be trivially inferrable; why won't the compiler do it in this particular context?

... This is a semi-XY situation; I'm only using closures in the first place because the rules for intertwingling tokens from macro body and macro arguments won't let me do this instead

macro_rules! cstrv_from {
    ($ty:ty) => {
        impl From<$ty> for CStrV {
            fn from(v: $ty) -> CStrV {
                CStrV::new(v)
            }
        }
    };
    ($ty:ty, $conv:expr) => {
        impl From<$ty> for CStrV {
            fn from(v: $ty) -> CStrV {
                CStrV::new(v.$conv)
            }
        }
    };
}

cstrv_from! { *const *const u8 }
cstrv_from! { *const *mut u8,  cast::<*const u8>() }
cstrv_from! { *mut *const u8,  cast_const() }
cstrv_from! { *mut *mut u8,    cast::<*const u8>().cast_const() }

so if anyone knows how to make something like that Do What I Meant, that'd be even better.

Here's one way.

There are special type inference rules that make closures work better when you pass the closure to a function that can constrain its signature, and that's the only case that really works well. It might be that calling the function isn't sufficiently constraining (a single set of arguments doesn’t fully determine the signature) or it might be that the compiler just doesn’t do that reasoning for immediate calls.

I think you can make this work easily by passing in the variable:

macro_rules! cstrv_from {
    ($ty:ty) => {
        impl From<$ty> for CStrV {
            fn from(v: $ty) -> CStrV {
                CStrV::new(v)
            }
        }
    };
    ($ty:ty, |$v:ident| $conv:expr) => {
        impl From<$ty> for CStrV {
            fn from($v: $ty) -> CStrV {
                CStrV::new($conv)
            }
        }
    };
}

cstrv_from! { *const *const u8 }
cstrv_from! { *const *mut u8,  |v| v.cast::<*const u8>() }
cstrv_from! { *mut *const u8,  |v| v.cast_const() }
cstrv_from! { *mut *mut u8,    |v| v.cast::<*const u8>().cast_const() }

The macro syntax now looks like a closure for recognizability, but isn't actually. But the key part that makes this work with “the rules for intertwingling tokens from macro body and macro arguments” is that the binding and usage of v are both passed in to the macro, rather than one passed and one written in the macro.

2 Likes

Yeah, that works, thank you.

I find it slightly unintuitive that it does work, because $v and $expr are two different macro arguments, and if we're going to have a rule that foo in the macro arguments is a different identifier than foo in the macro body, why isn't it that each macro argument creates its own distinct paint color for identifiers? Perhaps because that would be just too limiting to contemplate? But I trip over the hygiene rules every dang time I try to write a macro_rules macro, so I'm primed to expect them to not let me do anything anyway...

I could quibble that macros don't really have distinct “arguments”, just matching rules that destructure the single input token-stream, but the actually semantically important reason is that the “color” of the macro’s arguments is the same “color” as outside the macro call. The point of macro hygiene is that you can write

fn foo() {
    let x = Bar::new();
    some_macro!(x.do_something());
}

and you can know that x refers to the visible variable x regardless of what the macro expands to (except in cases where it’s an error due to e.g. your input not being treated as an expression); and in the other direction the macro can define local variables for its own purposes without needing to worry about you using the same names too.

If macros “recolored” their inputs then nearly all function-like macros, like vec![] and println!(), would be useless because they could not be called with expressions that mentioned any variables from outside the macro call.

It's even possible, though only rarely useful, for there to be “only one color” involved; this program compiles:

fn main() {
    let x = "hello world";

    macro_rules! m {
        () => { x }
    }
    
    println!("{}", m!());
}

You can think of this as m capturing the variable x, except instead of the value being captured, it's the name.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.