SRU
SRU

Reputation: 95

Why do I get the error "function pointers in const fn are unstable" but it goes away when wrapped in a newtype?

Is this intended behaviour or a compiler bug?

The following code does not compile. values in MyStruct is an Option since Vec::new is not a const fn - but Option::None is constant (but it still does not compile).

type MyFun = fn(input: u32) -> u32;

struct MyStruct {
    values: Option<Vec<MyFun>>,
}

impl MyStruct {
    pub const fn init() -> Self {
        Self { values: None }
    }
}

fn main() {
    MyStruct::init();
}

Playground

error[E0723]: function pointers in const fn are unstable
 --> src/main.rs:9:24
  |
9 |         Self { values: None }
  |                        ^^^^
  |
  = note: see issue #57563 <https://github.com/rust-lang/rust/issues/57563> for more information
  = help: add `#![feature(const_fn)]` to the crate attributes to enable

Using a newtype (Wrapped) solves the problem. This feels strange, since both examples are identical (at least the generated binaries should be identical). Is this behaviour intended or is that a bug?

This works:

type MyFun = fn(input: u32) -> u32;

struct Wrapped(Vec<MyFun>);

struct MyStruct {
    values: Option<Wrapped>,
}

impl MyStruct {
    pub const fn init() -> Self {
        Self { values: None }
    }
}

fn main() {
    MyStruct::init();
}

Playground

Upvotes: 2

Views: 1323

Answers (1)

oli_obk
oli_obk

Reputation: 31263

TLDR: A bug, but not the one you were hinting at. We are overaggressive in keeping the door open for future features in const fn.

The error you are encountering was created in order to prevent users from writing functions like

const fn foo(f: fn()) {}

since it is impossible to use f in a callable way. The following function is illegal right now.

const fn foo(f: fn()) { f() }

At the time of stabilization of const fn we weren't sure whether it should be legal, so we preemptively forbade fn pointers in const fn. The same goes for creating function pointers within the const fn. So

const fn foo() {
    let f: fn() = some_function;
}

is forbidden, because we want to keep the door open for permitting

const fn foo() {
    let f: fn() = some_function;
    f();
}

If you expand your use case's code, you get something like

pub const fn init() -> Self {
    let values: Option<fn(u32) -> u32> = None;
    Self { values }
}

You are correct in that this is a bug. Though not due the difference between the wrapped and non-wrapped version, but because the None never actually creates a function pointer. We just decided to be overly aggressive in the check in order to not miss anything. It is definitely possible to create a better analysis here, though we may just want to implement function pointers within const fn.

The reason there's a difference between the wrapped and non-wrapped version is that we want to allow

const fn foo(f: fn()) { f() }

but not

const fn foo(wrapper: Wrapper) { (wrapper.f)() }

as the latter doesn't show the function pointer in the API, so we can't do any magic to ensure that the function pointer points to a const fn.

The difference between the wrapped and non-wrapped version may be confusing enough to force us to abandon the idea that we can just invoke fn pointers in const fn as described above and come up with a new scheme for fn pointers in const fn.

Upvotes: 6

Related Questions