Skip to content
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

some editing to better reflect my concern #1

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions text/0000-const-generic-const-fn-bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,12 @@ This is strictly the least restrictive and generic variant, but is a semver
hazard as changing a const fn's body to suddenly call a method that it did not before can break
users of the function.

# Unresolved questions

* Is it possible to ensure that we are consistent about opt-in vs opt-out of
constness in static trait bounds, function pointers, and `dyn Trait` while
remaining backwards compatible? Also see [this discussion][dynamic_dispatch].

# Future work

This design is explicitly forward compatible to all future extensions the author could think
Expand Down Expand Up @@ -610,26 +616,40 @@ trait Foo {
which does not force `impl const Foo for Type` to now require passing a `T` with an `impl const Bar`
to the `a` method.

## `const` function pointers
## `const` function pointers and `dyn Trait`
[dynamic_dispatch]: #const-function-pointers-and-dyn-trait

The RFC discusses const bounds on *static* dispatch. What about *dynamic* dispatch?
In Rust, that means function pointers and `dyn Trait`.

### Function pointers

This is illegal before and with this RFC:

```rust
const fn foo(f: fn() -> i32) -> i32 {
f()
}
```

is illegal before and with this RFC. While we can change the language to allow this feature, two
questions make themselves known:
To remain consistent with trait bounds as described in this RFC, it seems
reasonable to assume that a `fn` pointer passed to a `const fn` would implicitly
be required to point itself to a `const fn`, and to have an opt-out with
`?const` for cases where `foo` does not actually want to call `f` (such as
`RawWakerVTable::new`).

However, there are two problems with this:

1. fn pointers in constants

```rust
const F: fn() -> i32 = ...;
```

is already legal in Rust today, even though the `F` doesn't need to be a `const` function.
Since we can't reuse this syntax, do we need a different syntax or should we just keep constants
as they are and just reuse the syntax in `const fn` arguments?
is already legal in Rust today, even though the `F` doesn't need to be a
`const` function. Since we can't reuse this syntax, do we need a different
syntax or should the same syntax mean different things for `const` types and
`const fn` types?

2. Opt out bounds might seem unintuitive?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not edit this part, but given that the exact same thing is called "natural" for dyn Trait, I feel we should adjust one of the two sentences. :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not my opinion. Iirc I just added it because someone mentioned it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what about removing both this and the "natural", basically avoiding making a judgment?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think it's "natural" or at least consistent to keep dyn some trait specifiers in sync with T: some trait specifiers

Copy link
Author

@RalfJung RalfJung Jun 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with that. And moreover, I think it is just as natural to keep dyn <specifiers> Trait in sync with <specifiers> fn (both of these are types here, not items/delcarations). Conversely, I think an asymmetry between our two kinds of dynamic dispatch (fn ptrs and dyn Trait) is just as unnatural as an asymmetry between traits dispatching statically or dynamically.

So I am fine with keeping the "natural" here for traits if we also call the same thing "natural" for function pointers.

Copy link
Author

@RalfJung RalfJung Jun 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern fn ptrs make no sense -- extern is a property of a function definition, but irrelevant for its type. (We have extern "ABI" fn ptrs, but here extern is just a bad keyword that we use to explicitly specify the ABI. extern "Rust" fn is the exact same thing as fn.)

We don't need dyn unsafe Trait because the Trait declares its methods and whether they are unsafe. That is just like saying that function pointers come with argument and return types which dyn Trait does not -- it is true, and IMO does not change the argument here. It just reflects that dyn Trait is nominal while function pointers are structural.

Imagine a structural version of dyn Trait. It would be dyn trait { fn method1(i32); unsafe fn method2(i64); }. Now a fn ptr is very much the same thing as a dyn trait {...} with a single function. Does this make the symmetry clearer?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, ok, the symmetry in semantics is clear, but I was mainly worried about syntax here 😆

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah -- I don't care about syntax, as long as it is consistent. ;) Which is what this is not, hence my comments.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what about removing both this and the "natural", basically avoiding making a judgment?

ok let's do that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I also reordered the fn ptr and dyn Trait discussion, because as you said dyn Trait is closer to what the RFC discusses otherwise, so this makes for a smoother transition I think.


Expand Down Expand Up @@ -661,26 +681,14 @@ fn foo(f: const fn() -> i32) -> i32 {
}
```

Which is useless except for ensuring some sense of "purity" of the function pointer ensuring that
Which could be useful for ensuring some sense of "purity" of the function pointer ensuring that
subsequent calls will only modify global state if passed in via arguments.

## explicit `const` bounds
[explicit_const]: #explicit-const

`const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to
replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in

```rust
fn foo<T: const Bar>() -> i32 {
const FOO: i32 = T::bar();
FOO
}
```

Which, once `const` items and array lengths inside of functions can make use of the generics of
the function, would allow the above function to actually exist.
However, this would be inconsistent with what the RFC proposes for traits. That
is particularly surprising when we consider that the exact same concern applies
to `dyn Trait`.

## `dyn Trait`
### `dyn Trait`

A natural extension to this RFC is to allow

Expand All @@ -698,6 +706,34 @@ const fn foo(bar: &dyn ?const Trait) -> SomeType {
}
```

However, the same concerns as for function pointers apply. In particular, this
is already allowed on stable, without any check that the `Trait` implementation
is `const`:

```rust
const F: &dyn Trait = ...;
```

Like with function pointers, we could instead make `dyn Trait` opt-in to
constness with `dyn const Trait`, but that would be inconsistent with static
trait bounds.

## explicit `const` bounds
[explicit_const]: #explicit-const-bounds

`const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to
replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in

```rust
fn foo<T: const Bar>() -> i32 {
const FOO: i32 = T::bar();
FOO
}
```

Which, once `const` items and array lengths inside of functions can make use of the generics of
the function, would allow the above function to actually exist.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

Expand Down