Open gnzlbg opened 5 years ago
I think that, at the very least, this should work:
const fn foo() {}
const FOO: const fn() = foo;
const fn bar() { FOO() }
const fn baz(x: const fn()) { x() }
const fn bazz() { baz(FOO) }
For this to work:
const
must be part of fn
types (just like unsafe
, the extern "ABI"
, etc.)const fn
types from const fnCurrently, const fn
s already coerce to fn
s, so const fn
types should too:
const fn foo() {}
let x: const fn() = foo;
let y: fn() = x; // OK: const fn => fn coercion
I don't see any problems with supporting this. The RFC mentions some issues, but I don't see anything against just supporting this restricted subset.
This subset would be super useful. For example, you could do:
struct Foo<T>(T);
trait Bar { const F: const fn(Self) -> Self; }
impl<T: Bar> Foo<T> {
const fn new(x: T) -> Self { Foo(<T as Bar>::F(x)) }
}
const fn map_i32(x: i32) -> i32 { x * 2 }
impl Bar for i32 { const F: const fn(Self) -> Self = map_i32; }
const fn map_u32(x: i32) -> i32 { x * 3 }
impl Bar for u32 { const F: const fn(Self) -> Self = map_u32; }
which is a quite awesome work around for the lack of const
trait methods, but much simpler since dynamic dispatch isn't an issue, as opposed to:
trait Bar { const fn map(self) -> Self; }
impl Bar for i32 { ... }
impl Bar for u32 { ... }
// or const impl Bar for i32 { ... {
This is also a way to avoid having to use if
/match
etc. in const fn
s, since you can create a trait with a const, and just dispatch on it to achieve "conditional" control-flow at least at compile-time.
AFAIK const fn
types are not even RFC'd, isn't it too early for a tracking issue?
Don't know, @centril suggested that I open one.
I have no idea why const fn
types aren't allowed. AFAICT, whether a function is const
or not is part of its type, and the fact that const fn
is rejected in a type is an implementation / original RFC oversight. If this isn't the case, what is the case?
EDIT: If I call a non-const fn
from a const fn
, that code fails to type check, so for that to happen const
must be part of a fn
type.
AFAIK
const fn
types are not even RFC'd, isn't it too early for a tracking issue?
Lotsa things aren't RFCed with respect to const fn
. I want these issues for targeted discussion so it doesn't happen on the meta issue.
If I call a non-const fn from a const fn, that code fails to type check, so for that to happen const must be part of a fn type.
it is.
you can do
const fn f() {}
let x = f;
x();
inside a constant. But this information is lost when casting to a function pointer. Function pointers just don't have the concept of of constness.
Figuring out constness in function pointers or dyn traits is a tricky questions, with a lot of prior discussion in the RFC and the pre-RFC.
whats about?
pub trait Reflector { fn Reflect(&mut self)-> (const fn(Cow<str>)->Option<Descriptor>); }
I was fiddling with something while reading some of the discussion around adding a lazy_static
equivalent to std and found that this check forbids even storing a fn
pointer in a value returned from a const fn
which seems unnecessarily restrictive given that storing them in const
already works. The standard lazy types RFC winds up defining Lazy
like:
pub struct Lazy<T, F = fn() -> T> { ... }
Here's a simple (but not very useful) example that hits this.
Adding another type parameter for the function makes it work on stable but it feels unnecessary.
Could this specific case be allowed without stabilizing the entire ball of wax here? (Specifically: referencing and storing fn
pointers in const fn
but not calling them.)
Could this specific case be allowed without stabilizing the entire ball of wax here? (Specifically: referencing and storing fn pointers in const fn but not calling them.)
The reason we can't do this is that this would mean we'd lock ourselves into the syntax that fn()
means a not callable function pointer (which I do realize constants already do) instead of unifying the syntax with trait objects and trait bounds as shown in the main post of this issue
Just in case other people run into this being unstable: It's still possible to use function pointers in const fn
as long as they're wrapped in some other type (eg. a #[repr(transparent)]
newtype or an Option<fn()>
):
#[repr(transparent)]
struct Wrap<T>(T);
extern "C" fn my_fn() {}
const FN: Wrap<extern "C" fn()> = Wrap(my_fn);
struct Struct {
fnptr: Wrap<extern "C" fn()>,
}
const fn still_const() -> Struct {
Struct {
fnptr: FN,
}
}
If const is a qualifier of function like unsafe
or extern
we have next issue:
const fn foo() { }
fn bar() { }
fn main() {
let x = if true { foo } else { bar };
}
It compiles now but not compiles with these changes because if
and else
have incompatible types.
So it breaks an old code.
If const is a qualifier of function like
unsafe
orextern
we have next issue:const fn foo() { } fn bar() { } fn main() { let x = if true { foo } else { bar }; }
It compiles now but not compiles with these changes because
if
andelse
have incompatible types. So it breaks an old code.
Const fn's are still fns, the type won't be charged. In fact const qualifier only allows const fn appear in const contexes, and be evaluated at compile time. You can think of this like implicit casting.
unsafe fn foo() { }
fn bar() { }
fn main() {
let x = if true { foo } else { bar };
}
This code does not compile for the same reason. Unsafe fn's are still fns too, why we haven't implicit casting in this situation?
unsafe fn foo() { } fn bar() { } fn main() { let x = if true { foo } else { bar }; }
This code does not compile for the same reason. Unsafe fn's are still fns too, why we haven't implicit casting in this situation?
This leads to possible unsafety in our code, and all which comes with it. const
fn casting on otherside don't brings any unsafety, so is allowed, const fn must not have any side effects only, it is compatible with fn contract.
fn bar() {}
const fn foo(){}
const fn foo_bar(){
if true { foo() } else { bar() };
}
This must not compile, because bar is not const and therefore can't be evaluated at compile time. Btw, it raises(?) "can't call non const fn inside of const one", and can be considered incorrect downcasting. (set of valid const fns is smaller than set of fns at all)
fn main() { let x = if true { foo } else { bar }; }
This code does not compile for the same reason. Unsafe fn's are still fns too, why we haven't implicit casting in this situation?
This leads to possible unsafety in our code, and all which comes with it.
const
fn casting on otherside don't brings any unsafety, so is allowed, const fn must not have any side effects only, it is compatible with fn contract.
I think you're missing the point. With implicit coercions, the type of x
would be unsafe fn()
, not fn()
. There's nothing about that which leads to possible unsafety. Generally, const fn()
can be coerced to fn()
and fn()
can be coerced to unsafe fn()
. It just doesn't happen automatically, which is why changing const fn foo()
to coerce into const fn()
rather than fn()
implicitly is a breaking change.
fn bar() {} const fn foo(){} const fn foo_bar(){ if true { foo() } else { bar() }; }
This must not compile, because bar is not const and therefore can't be evaluated at compile time. Btw, it raises(?) "can't call non const fn inside of const one", and can be considered incorrect downcasting. (set of valid const fns is smaller than set of fns at all)
Of course this must not compile, but I don't think that's related to what @filtsin was talking about.
Personally I would love to see more implicit coercions for function pointers. Not sure how feasible that is though. I've previously wanted trait implementations for function pointer types to be considered when passing a function (which has a unique type) to a higher-order generic function. I posted about it on internals, but it didn't receive much attention.
Generally,
const fn()
can be coerced tofn()
andfn()
can be coerced tounsafe fn()
. It just doesn't happen automatically, which is why changingconst fn foo()
to coerce intoconst fn()
rather thanfn()
implicitly is a breaking change.But not in oposite direction - thats what i wanted to say.
impl<T> Cell<T> {
pub fn with<U>(&self, func: const fn(&mut T) -> U) -> U;
}
Wouldn't const fn pointers make this sound? A const fn can't access a static or a thread-local, which would make this unsound.
Edit: a cell containing a reference to itself makes this unsound
It seems assignment is currently broken, complaining about casts, even though no such casts are actually performed. (A const function simply assigning a function ptr basically). https://github.com/rust-lang/rust/issues/83033
See my response at https://github.com/rust-lang/rust/issues/83033#issuecomment-831089437 -- short summary: there is in fact a cast going on here; see the reference on "function item types" for more details.
Just in case other people run into this being unstable: It's still possible to use function pointers in
const fn
as long as they're wrapped in some other type (eg. a#[repr(transparent)]
newtype or anOption<fn()>
):
I've found a case where this isn't true. (playground link)
struct Handlers([Option<fn()>; _]);
impl Handlers {
const fn new() -> Self {
Self([None; _])
}
}
Yea, just like with dyn Trait or generic trait bounds, there are workarounds to our checks and I'm convinced now we should just allow all of these like we do in const items. You can't call them, but you can pass them around.
For const closures a lot has happened in 2022.
This now works since rustc 1.61.0-nightly (03918badd 2022-03-07)
(commit range is 38a0b81b1...03918badd but I can't narrow it down to a single PR... maybe a side effect of #93827 ???):
#![feature(const_trait_impl)]
const fn foo<T: ~const Fn() -> i32>(f: &T) -> i32 {
f()
}
And since rustc 1.68.0-nightly (9c07efe84 2022-12-16)
(I suppose the PR was #105725) you can even use impl Trait
syntax:
#![feature(const_trait_impl)]
const fn foo(f: &impl ~const Fn() -> i32) -> i32 {
f()
}
For const fn pointers, nothing much has happened though. This still errors:
const fn foo(f: ~const fn() -> i32) -> i32 {
f()
}
gives
Edit: note that while what I said is great progress, the two aren't well comparable, as the Fn
trait support is for monomorphized generics cases, as in those where we know the type at const eval time and can derive the function from the type. dyn Fn
trait support is still not existent. This gives a bunch of errors:
#![feature(const_trait_impl)]
const fn foo(f: &dyn ~const Fn() -> i32) -> i32 {
f()
}
Seems to have been patched. You need #[const_trait]
attribute to use ~const
, which the Fn
trait doesn't have.
rust-std
says that it has "effects"
... #[must_use = "closures are lazy and do nothing unless called"] // FIXME(effects) #[const_trait] pub trait Fn<Args: Tuple>: FnMut<Args> { ...
Sub-tracking issue for https://github.com/rust-lang/rust/issues/57563.
This tracks
const fn
types and callingfn
types inconst fn
.From the RFC (https://github.com/oli-obk/rfcs/blob/const_generic_const_fn_bounds/text/0000-const-generic-const-fn-bounds.md#const-function-pointers):
const
function pointersis illegal before and with this RFC. While we can change the language to allow this feature, two questions make themselves known:
fn pointers in constants
is already legal in Rust today, even though the
F
doesn't need to be aconst
function.Opt out bounds might seem unintuitive?
Alternatively one can prefix function pointers to
const
functions withconst
:This opens up the curious situation of
const
function pointers in non-const functions:Which is useless except for ensuring some sense of "purity" of the function pointer ensuring that subsequent calls will only modify global state if passed in via arguments.