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

unsized params in traits #3745

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
168 changes: 168 additions & 0 deletions text/3696-unsized-params-in-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
- Feature Name: unsized_params_in_traits
- Start Date: 2024-12-18
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#134475](https://github.com/rust-lang/rust/issues/134475)

# Summary
[summary]: #summary

A (temporary) lint which detects unsized parameter in required trait methods, which will become a hard
Copy link
Contributor

Choose a reason for hiding this comment

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

error in the future


# Motivation
[motivation]: #motivation

This rfc is to prevent the use from making their trait unimplementable (in some cases, for unsized types)

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

Think of this lint as the `missing_fragment_specifier` feature (if thats what its called), it is only meant to be temporary and will be a hard error in the future
```rust
#![deny(unsized_params_in_traits)]

trait Foo {
fn bar(self);
}
```
the above code fails, because of the lint; this happens because here `Self: ?Sized`

Also look at this code:
```rust
#![deny(unsized_params_in_traits)] // this is default, but here for clearness

trait Foo {
fn bar(bytes: [u8]);
}
```
the above code would work without the lint (how did no one notice this?)
Copy link
Contributor

Choose a reason for hiding this comment

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

That kind of question doesn't belong in the RFC text


While both of the above _would_ work without the lint, you cant actually implement it
```rust
impl Foo for i32 {
fn bar(bytes: [u8]) {}
}
```
Produces:
```
error: The Size value for `[u8]` is not known at compile time
```

So in all: this rfc is to prevent confusion

Now, if you do notice, in the [summary], i did say `required methods`; provided methods are `Sized`-checked
```rust
trait Foo {
fn bar(self) {

}
}
```
Produces:
```
error: the size value for `Self` is not know at compile time
```
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

There is one feature this may clash with, and it is the feature for `unsized fn params`, the lint should be disabled when this feature is enabled; if that is possible, however if it may cause confusion to the user.

Here is the first example, but it clashes with the `unsized fn params` feature
```rust
#![feature(unsized_fn_params)]
// implicit #![deny(unsized_params_in_trait)]
#![allow(internal_features)]

trait Foo {
fn bar(bytes: [u8]);
}
```

The above code fails, while it shouldn't due to the feature

However, it is internal so it is semi-ok

this feature will be very simple to implement (i hope), just port the `Sized`-checking logic from provided methods to required methods, if that is possible (also maybe from regular functions) and throw an error/warning/nothing depending on the lint level.

here is some rust-pseudo code:
Copy link
Contributor

Choose a reason for hiding this comment

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

This kind of detail is not necessary.

```rust
if !req_function.
params
.filter(|param| !param.is_sized())
.is_empty() {
match lint_level {
Allow => (),
Warn => warn("..."),
Deny | Forbid => error("...")
}
}
```
replace the `...` with:
```
The size value for {param_type} is not know at compile time
# ...
This was previously accepted by the compiler, but it will become a hard error in the future!
# if it was the default value of 'deny' the next line would be added
`#[deny(unsized_param_in_trait)]` on by default
```
Obviously the above code isnt actually correct as it doesnt check _which_ param is unsized, it just checks if there is, (you can probably loop over the 'filter' object, and make an individual error for each one)

# Drawbacks
[drawbacks]: #drawbacks
Copy link
Contributor

Choose a reason for hiding this comment

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

One drawback is that it causes a lot of churn. The RFC should have a rationale for what the advantage is of adding a Self: Sized bound to e.g. IntoIterator::into_iter or Add:add.


- This could cause breaking changes, however the lint gives time for migration
- This could be intended for `dyn` compatibility (see [rationale-and-alternatives] for a way to fix this)
This drawback is about receivers, take this example
```rust
trait Bar {
fn foo(self);
}
```
if `self` was sized checked here, and the value _should_ be consumed, then this code would be impossible without a `Self: Sized` bound, but as you know, adding that bound removes `dyn-Compatibility`
```rust
let x: &dyn Bar = &10;
```
Produces:
```
Bar is not dyn compatible
# ...
```

# Rationale and alternatives
Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative could be to generate where Self: Sized bounds from the signature where applicable and thus never actually forbid it.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is what the Reference (inaccurately) says currently happens, for context.

source

https://doc.rust-lang.org/reference/items/traits.html#object-safety

[...] A trait is object safe if it has the following qualities [...] All associated functions must either be dispatchable from a trait object or be explicitly non-dispatchable: [...] Dispatchable functions must: [...] Not have a where Self: Sized bound (receiver type of Self (i.e. self) implies this). [...] Explicitly non-dispatchable functions require: Have a where Self: Sized bound (receiver type of Self (i.e. self) implies this).

(emphasis mine)

Copy link
Member

Choose a reason for hiding this comment

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

This would, for the record, break the dyn compatibility of FnOnce.

Copy link
Contributor

Choose a reason for hiding this comment

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

So does this RFC. And we'd have to revert this RFC whenever we figure out generally allowing folk to call owned methods on Box<dyn TheirTrait>

[rationale-and-alternatives]: #rationale-and-alternatives

This design is best as it helps users to migrate their code before it breaks
ionicmc-rs marked this conversation as resolved.
Show resolved Hide resolved

Other Considered Designs:
- as mentioned in [drawbacks], this may be intentional with `receivers` for `dyn` Compatibility
so another design is dividing this lint into two, one for receivers, and one for regular parameters
- Not making it a hard error, which could work but it may cause users to make their traits unimplementable.
- A direct hard error, though not recommended for migration purposes (mentioned above)
- Leaving it be, though again not recommended as mentioned in [motivation]

The impact of not doing this:
May cause confusion because a parameter is unsized, and the trait cannot be implemented, which is not good.

This may also cause an `ICE` in some way because the parameters are unsized

# Prior art
[prior-art]: #prior-art

This feature is not a problem in other languages, weather a type is `Sized` or not, it is abstracted away and you can never know

For example: C++ does not really have unsized types (that i know of)
Another: C# abstracts the idea of a `Sized` value

Higher level languages do not need to run on the machine directly so there is no need to know the size of a value
Copy link
Contributor

Choose a reason for hiding this comment

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

These examples are the opposite of prior art. They explain where there's no prior art. Maybe you can find similar requirements in Rust? I think function pointers are similarly definable with arguments that no real function can have

Copy link
Contributor

Choose a reason for hiding this comment

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

The trivial bounds feature has similar situations, too


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

- Should this be a lint, or something else
- Does this need to become a hard error

# Future possibilities
[future-possibilities]: #future-possibilities

None, this isnt really something that will stay that long