`fn(..) -> _` as shorthand for `fn(_, _, _, ...) -> _`

Currently if you want to cast “real” (named) function to a function pointer, you have to count out the number of arguments in order to write the type of the fn type on the RHS of the cast. So it'd be something like my_func as fn(_, _, _) -> _. If you don't have the right number of underscores in the arguments list, it won't compile.

It would be handy to allow my_func as fn(..) -> _ to mean “any number of arguments” so that you don't have to write them all out. I think this meshes well with the current language design and would be the way most users who reached for this feature would attempt to spell it.

(A more extreme change in this direction would be to make an even shorter shorthand for function pointers, e.g., my_func as fn->, which would be short — but perhaps too short.)

10 Likes

This may be confusing, because this notation can be interpreted as "function with any number of arguments". I.e. I think this code can be confusing:

fn foo() {}
fn bar(_: u32) {}

let f = if cond {
    foo as fn(..) -> _
} else {
    bar as fn(..) -> _
};

Is there a point in using a catch-all for arguments, while leaving the possibility to explicitly specify return type? Also there could be some confusion whether we could specify parts of the argument list while omitting others, like with tuples (fn(Foo, .., Bar) -> _). There may also be conflicts if variadic functions are ever added in the future.

That said, there is a unique function pointer type which a function or non-capturing closure could be converted to. So perhaps we could simplify the notation even further, to something like foo as fn?

3 Likes

Whilst foo as fn has its appeal, I think it's important to bear in mind that casting functions with different signatures as fn will result in different types of function pointer—which this proposed syntax particularly masks, and I suspect may lead to confusion.

1 Like

Right, the primary goal of the proposed syntax is to have something between the underscore in my_func as _ and the function pointer my_func as fn(_, _, _) -> _. fn(..) -> _ is one option, and it fits into the existing syntax (e.g., you could replace the underscore with an actual type). But it would also be nice to have an even shorter syntax that meant “the only kind of function pointer this could possibly be”. I think fn-> is not great, not terrible.

I think my_func as _ is the shorthand right now. Where that doesn't work hopefully suggestions in warnings and/or rust analyzer fixups/suggestions can help bridge the gap of manually counting arguments. And when you need to type this a lot hopefully type aliases can help.

Which is all to say you may be highlighting a legitimate need, I'm not sure, but some motivating examples might help delineate the solution space a little better. Especially since fn(..) looks like some tasty syntax to leave open for variable argument functions.

If you're writing a macro, you'll need the invoker to supply the correct function pointer pattern, which is annoying. It would be nice to have some syntax for "cast to function pointer" without knowing anything about its signature.

1 Like

How much of this is just about as, vs using in other scenarios?

If it's for the casting scenario primarily, then I'd suggest starting with the easier change of some sort of function or method -- like was just added to nightly for turning a reference into a pointer: add ptr::from_{ref,mut} by RalfJung · Pull Request #104977 · rust-lang/rust · GitHub

(That said, I think I'm generally positive on the idea of extending .. to more places. It's come up for allowing foo::<i32, ..>() before too, for example.)

I am also reminded of another use-case that popped up in this question on StackOverflow a while back:

2 Likes

How would you define a method or function that did that without variadic generics?

Ideally I think we'd have a trait to match the other Fn* traits like

trait AsFnPointer<A, B, C, ...>: Copy {
    type Output;
    fn as_fn_pointer(self) -> fn(A, B, C, ...) -> Self::Output;
}

but aiui the way the Fn* traits work around the lack of variadic generics wouldn't extend to a trait like that without some sort of new compiler magic.

2 Likes

This does work quite well for extern "Rust" fn :100:, but alas won't for unsafe fns and/or fns with another ABI :pensive:

1 Like

I believe you can use the following:

macro_rules! as_fn_pointer_trait {
	($name:ident; $($types:ident),+ $(,)?) => {
		trait $name: Copy {
			$(type $types;)*
			type Output;
			fn as_fn_pointer(self) -> fn($(Self::$types,)*) -> Self::Output;
		}
		impl<$($types,)* Output> $name for fn($($types,)*) -> Output {
			$(type $types = $types;)*
			type Output = Output;
			fn as_fn_pointer(self) -> fn($(Self::$types,)*) -> Self::Output {
				self as _
			}
		}
	}
}

Which you can now invoke as

as_fn_pointer_trait!(AsFnPointer4; A, B, C, D);
// becomes...
trait AsFnPointer4: Copy {
	type A;
	type B;
	type C;
	type D;
	type Output;
	fn as_fn_pointer(self) -> fn(Self::A, Self::B, Self::C, Self::D) -> Self::Output;
}
impl<A, B, C, D, Output> AsFnPointer4 for fn(A, B, C, D) -> Output {
	type A = A;
	type B = B;
	type C = C;
	type D = D;
	type Output = Output;
	fn as_fn_pointer(self) -> fn(Self::A, Self::B, Self::C, Self::D) -> Self::Output {
		self as _
	}
}