-
Notifications
You must be signed in to change notification settings - Fork 13.4k
fn_cast!
macro
#140803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
So in such a world, the docs for the macro would say that this generates a new function? Because otherwise it seems like this list here has to account for the macro as well. The macro needs to be unsafe of course, since function arguments are still being transmuted. We could have the macro ensure that the signatures are ABI-compatible -- but this can only be fully checked during monomorphization. |
Well, yes it semantically creates a new function even if it has the same address. How exactly we word that is up to debate. I guess we might not want provenance for function pointers (?), so if |
I mean, we could.^^ But yeah it's probably better if we avoid using provenance wherever possible. |
We have unsafe function pointers. So I wonder, should the macro call be unsafe, or should we be returning an
If we were to do these checks, I wonder whether we might want to support this as a coercion or let _: *const () = &(); Does it make any sense to allow?: let _: unsafe fn(*const ()) = |&()| (); |
I don't think I'd like to make |
The macro itself needs to be unsafe. Otherwise, how do people get a non-unsafe fn pointer? By transmuting the output of |
While the function signature change itself can’t cause UB without the function being called, asking the call sites to justify the safety of the implied arg/result transmutes leads to somewhat silly consequences:
It’s tempting to say: At the same time, there are cases where a safe function is type-punned into something that creates significant extra safety conditions for callers (e.g., type erasing So I think it’s probably most useful to consider “safe fn <-> unsafe fn” to be part of the type punning that |
Interesting. That's not how I think about it. I think of the point of the built-in as being to do something a lot smarter than what's otherwise possible so as to support CFI, in terms of modifying the list of signatures when it can, generating and using a trampoline only when needed, etc. If it were just about getting rid of a transmute, I don't think we'd do this.
Perhaps you could describe the use case you have in mind for when the cast function pointer will be safe to call. What's coming to my mind, in terms of practical use cases, are all ones where it would not be. |
Let me adjust my phrasing: yes, CFI compatibility is ultimately "the point" but I don't think this can be usefully separated from removing function pointer transmutes. To make CFI work, you need an intentional marker for "this specific function can also be called with this specific signature different from what its definition said" (which then enables e.g. generating the right trampoline if one is needed) rather than transmutes that leave you guessing whether the signature mismatch may be unintentional. Carving out a subset of such transmutes that are "still okay" after the introduction of
This example is a bit speculative for several reasons, but it's inspired by real code I'm working on. Consider a library that defines a trait for "fieldless impl<I: /* ... */, T> Array<I, T> {
fn for_each_with_index_erased<F: FnMut(I, &T)>(f: F) {
// SAFETY: `I` is a `repr(u8)` enum, so it's sound to transmute into u8
for_each_with_index_raw(&self.0, unsafe { fn_cast!(f) })
}
}
fn for_each_with_index_erased<T>(elems: &[T], f: fn(u8, &T)) {
for (i, elem) in elems.iter().enumerate() {
f(i as u8, elem);
}
} One reason this is speculative is I don't think there's a bound I could put on the type parameter |
Another reason why the above example is speculative: the proposal says that the function being cast can’t have any captures if it’s a closure. It’s not clear to me why that restriction would be needed. Couldn’t the compiler generate another closure type that has the same captures, is ABI-compatible with the original closure type, and implement the appropriate Of course this doesn’t have to be part of the initial feature but I’d like to know if it’s possible in principle or if there’s a fundamental problem I’m missing. |
In discussion with @Darksonn I toyed the idea that |
Uh oh!
There was an error while loading. Please reload this page.
Since Rust 1.76 we document that it's valid to transmute function pointers from one signature to another as long as their signatures are ABI-compatible. However, we have since learned that these rules may be too broad and allow some transmutes that it is undesirable to permit. Specifically, transmutes that change the pointee type or constness of a pointer argument are considered ABI-compatible, but they are rejected by the CFI sanitizer as incompatible. See rust-lang/unsafe-code-guidelines#489 for additional details and #128728 for a concrete issue.
This issue tracks a proposed solution to the above: Introduce a new macro called
fn_cast!
that allows you to change the signature of a function pointer. Under most circumstances, this is equivalent to simply transmuting the function pointer, but in some cases it will generate a new "trampoline" function that transmutes all arguments and calls the original function. This allows you to perform such function casts safely without paying the cost of a trampoline when it's not needed.The argument to
fn_cast!()
must be an expression that evaluates to a function item or a non-capturing closure. This ensures that the compiler knows which function is being called at monomorphization time.As a sketch, you can implement a simple version of the macro like this:
This implementation should get the point across, but it is incomplete for a few reasons:
fn_cast!
should be improved to work with functions of any arity.fn(&T)
tofn(*const T)
is allowed because&T
and*const T
is treated the same by KCFI. The compiler could detect such cases and emit a transmute instead of a trampoline.By adding this macro, it becomes feasible to make the following breaking change to the spec:
Here, the change is that ABI-compatible calls are considered EB. However, even without the spec change the macro is useful because it would allow for a more efficient implementation of #139632 than what is possible today.
This proposal was originally made as a comment. I'm filing a new issue because T-lang requested that I do so during the RfL meeting 2025-05-07.
The text was updated successfully, but these errors were encountered: