-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Allow type inference for const or static #3546
base: master
Are you sure you want to change the base?
Conversation
# Motivation | ||
[motivation]: #motivation | ||
|
||
Rust currently requires explicit type annotations for `const` and `static` items. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to write a little bit about why Rust is like this currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this should have a longer explanation of Rusts "rule" of "no inference in signatures", how this RFC is breaking it and why this is okay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion! I actually don't know why the design was made that way. Maybe someone from the Rust team can help explain?
The "type is missing in const" error was emitted from the parser so my guess would be that it was just difficult to infer types when consts were implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's like this currently because it was decided that all public API points should be "obviously semver stable" rather than "quick to type".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for explaining this. I've incorporated it into the RFC.
text/0000-const-type-inference.md
Outdated
from the initial value. For example: | ||
|
||
```rs | ||
const PI = 3.1415; // inferred as f64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would personally prefer that the actual type of numeric or float types be known rather than picking arbitrary defaults (e.g. i32
or f64)
. I'm not too keen on it in a local context either tbh but there it's mitigated by the compiler being able to infer the real type from the surrounding code most of the time.
const PI = 3.1415; // error
const PI = 3.1415_f64; // inferred as f64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer more consistent behavior with let bindings. So let's make a simple vote here:
🎉: const PI = 3.1415; // error
🚀: const PI = 3.1415; // inferred as f64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a big issue for me either way it's just that a few times I've only later realised a type has been unexpectedly made i32
. But it's easily fixed and more of an annoyance than a problem per se. It doesn't help that an i32
is very rarely what I want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it follows the normal rust inference default types then it's at least no worse than what let
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it's worse in the sense that let can use future code to infer the type, so mostly this is a non issue there unless there's a lot of generics involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I would most like if the const was of {float}
type but people aren't ready for that conversation maybe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Co-authored-by: Nilstrieb <[email protected]>
text/0000-const-type-inference.md
Outdated
```rs | ||
const PI = 3.1415; // inferred as f64 | ||
static MESSAGE = "Hello, World!"; // inferred as &'static str | ||
const FN_PTR = std::string::String::default; // inferred as fn() -> String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
technically the const
would have the function item's type instead of a function pointer type.
https://doc.rust-lang.org/reference/types/function-item.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a good point - I guess there's not much point to coerce the function item's type into function pointer type for const
items. But should we coerce for static
items? That way you get to reassign the static
items with functions of the same signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused about why a person would want it as a static at all, so perhaps we shouldn't allow it at all in the first version of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lokathor maybe they just use the const as a quick switch between two cfg implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense as a const
but little sense as a static
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the type is a ZST, like function item types, at runtime, const
and static
are essentially interchangable. when the type is a function pointer, unless wrapped in something with interior mutability, static
is basically just a const
with a stable address, IIRC LLVM will still constant-propagate the value to most uses, since it's marked read-only so LLVM knows the value won't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing implicit coercions into an unknown target type sounds very confusing. If the right-hand side is String::default
, then the resulting type should be the actual type of the expression, the function item ZST.
Co-authored-by: teor <[email protected]>
Co-authored-by: teor <[email protected]>
Make that a rustc lint, and make it warn by default, unless
Then I'd be very happy with this proposal. One open question is to what extent type placeholders should be allowed, and if they should silence the lint: const X: _ = 3.14;
const Y: [_; 4] = [1, 2, 3, 4]; |
I'd like to point out that all of the mentioned downsides also apply to function return types. The main difference is that functions support const FOO: impl Fn() -> i32 = || {
todo!();
}; |
text/0000-const-type-inference.md
Outdated
However, not all `const` or `static` items are public, and explicit typing isn't always important for semvar stability. | ||
Requiring explicit typing for this reason seems a bit heavy handed. | ||
|
||
Both of these drawback could be addressed using an allow-by-default clippy lint for `const` and `static` types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think an allow-by-default lint addresses concerns about it being potentially confusing. Having a lint can be useful, but it doesn't address the problem because almost everyone won't be using it (but may be using inference).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, happy to withdraw this suggestion and go with what you've suggested instead.
Co-authored-by: Nilstrieb <[email protected]>
Co-authored-by: Nilstrieb <[email protected]>
Many of the cases where this would be helpful would also be addressed by type_alias_impl_trait; in particular, it helps with dealing with unnameable types and macro / code generator output, without the drawbacks of loss of clarity and semver trouble (because the trait is guaranteed, and is also the only property usable in other crates). This may not be an argument against this RFC, but at least warrants discussion in the alternatives. |
That's a good thing to note as an alternative, but it won't help with array lengths, which are one of the ways the current rules frequently create pain (especially for static ARRAY_OF_UNINTERESTING_LENGTH = [
"foo",
"bar",
// ...
]; In the cases where a TAIT such as |
I feel like providing explicit types for values should be assumed to be the default, and use of the // I prefer this
const FOO: _ = "hello";
// over this
const FOO: "hello"; The use of placeholders also I think works better in the event of a "partially-nameable" type, e.g.: struct MyWrapper<T>(T);
// We can name `MyWrapper`, but not its generic argument.
static FOO: MyWrapper<_> = MyWrapper(|| todo!()); Also, for type inference around literals specifically, I'd prefer it not guess between options. I think this could lead to some confusion around things like this: fn takes_u8(_: u8) {}
fn takes_u16(_: u16) {}
// Demonstrating type inference on local variables.
fn local() {
let foo = 7;
// Uncommenting either of these lines works, but uncommenting both results in a compile error.
// takes_u8(foo);
// takes_u16(foo);
// If both lines are commented, `foo` is an i32; otherwise, the uncommented line changes its type.
}
// Demonstrating type inference on static variables.
fn local() {
static foo: _ = 7; // Inferred as i32 since there's no uses.
// Can uncommenting one of the below lines work?
// I don't like uses of a static changing its value, but it also feels wrong to let inference change a literal's inferred type based on uses in locals, but not statics.
// takes_u8(foo);
// takes_u16(foo);
} I'd prefer if literals still have to be explicitly annotated (either on the static definition or in the literal itself).
Given that the main use-case IMO is for types that can't be named, I think making a deny-by-default lint in rustc for a placeholder in the place of anything nameable (except for macro-generated code) would also be a good idea. And once TAIT is stabilized, I think it should expand to also lint any type in a public API (but not array lengths), regardless of whether it's macro-generated or nameable, since that can be used instead. |
I do think that requiring at least a Given the semver compatibility concerns, I also think that it's a good idea to require numerical types used for inference to have explicit typing. I've updated the RFC to reflect this. However, I don't think a deny-by-default lint is a good idea. Some types can just be too cumbersome to type, and I think we should leave this decision up to the users. |
in that case I think it would be totally reasonable to add an |
No. This is nothing to do with publicity or locality, but the intent of the API author. Until the language supports the feature to express "unspecified" items explicitly in the syntax, it is legitimate to encode the concrete type in the public code but annotated with documentation to caveat users about the fact that parts of them are implementation details and subject to change. |
Here's my perspective as someone new to Rust: I was confused by the compiler asking me to add a type for a I would support making type declarations optional for any I wouldn't support automatic type inference for numeric literals or other values where the literal value could fit multiple types. I think the user should always know what type is coming out of the |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as abuse.
This comment was marked as abuse.
This comment was marked as abuse.
This comment was marked as abuse.
This comment was marked as resolved.
This comment was marked as resolved.
FTR, I personally think the array issue is persuasive. If I've got a local static SYSCALLS: _ = [/*build syscall array*/]
global_asm!(
".globl __syscall_dest_lstar",
"__syscall_dest_lstar:"
"cmp eax, {SYSCALL_LIMIT}",
"jae 2f",
"mov rax, qword ptr [{SYSCALLS}+rip]",
"cmp rax, rax",
"jz 2f",
"swapgs",
"push rcx",
"push rsp",
"mov rsp, gs:[0]",
"mov rcx, r10",
"call rax",
"pop rsp",
"pop rcx",
"swapgs",
"sysret",
"2:",
"mov eax, {ERROR_UNSUPPORTED_KERNEL_FUNCTION}",
"sysret",
ERROR_UNSUPPORTED_KERNEL_FUNCTION = const errors::UNSUPPORTED_KERNEL_FUNCTION,
SYSCALLS = sym SYSCALLS,
SYSCALL_LIMIT = const SYSCALLS.len()
); It's fairly simple - check if the syscall number (in eax/rax) is below the syscall limit, if ts not, then return |
I think this is too broad, and we shouldn't do it for the same reason we're not planning on accepting leaving off function return types. We've talked about similar things in the past, like we once discussed So rather than just leaving this open for another year, I propose Note that I see that as "not this way", not that we'd be rejecting anything at all in this space. Specifically, I think that allowing And I also look forward to other things that allow simplifying the RHS of the static HIGH_COMPRESSION: CompressionOptions = .{
lookback_size: 1 << 20,
symbol_size: 9,
dictionary_length: 1 << 10,
}; Then you'd still be able to avoid re-mentioning the |
Team member @scottmcm has proposed to close this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
use static HIGH_COMPRESSION: CompressionOptions = _ { ... }; |
Would allowing type elision within the scope of a function be a plausible direction? |
I think it's good that items are items with item rules, regardless of where you put them. (Just speaking for me, not for the team.) If you have a |
I'm going to abstain from checking a box here. In any case, I do hope we manage to get as far as inferring array lengths one day. |
But |
I'm open to this happening in the future and don't think it should be closed as "never going to do this" from the team. This is a weird situation for our process – do I file a concern to block closing the RFC and likely continue to leave it open indefinitely without resolution? I feel funny doing that, but it would be better than making a statement from the team that I don't agree with. @rfcbot concern but what if we did The most important property of item types being explicit in Rust, in my view, is that there is no global type inference taking place across the entire program. This RFC is not proposing that. If the type of a const is clear from its definition site, it's a matter of style and clarity whether to include it. We can go on making this choice for our users, or we can be open to cases where it's not helping and let users make it themselves. I find an example like this well-motivating: const WRAPPED_PI: MyStruct<f32> = MyStruct(3.1415_f32); The type annotation is not adding clarity or value there. Furthermore, since bidirectional type inference has limited power in this context (we would not infer types based on usage of the const), the definition site is forced to be explicit about any ambiguous types that are not specified on the annotation itself. And we already have type inference within const sub-expressions today: If Do I think there will be cases where this makes code less clear? I'm sure there will, but don't expect them to be very common given the limited power of type inference in this context and the fact that we already do inference for sub-expressions. With that said, I would be enthusiastic about restricting the fallback behavior of type inference for integers and floats on const items, so that without the type annotation the On the precedent arguments, it looks like we didn't accept a special case for array lengths because const generics were supposed to fix it – but they haven't yet. I know some work is happening on const generics in H1 but we should really consider accepting a special case if it's not looking much closer by the end. I don't know the earlier reasoning for not accepting |
@tmandry The same argument could be applied to functions' return types. Should we also make them inferable? |
That's a whole separate discussion, please don't conflate the two things. |
@ChayimFriedman2 Valid point about my argument, but function bodies tend to be more complex than const items. For one, const items don't have arguments with their own complex types to keep track of. This feels to me like a difference in degree that in practice becomes more of a difference in kind. Yes there will be complex const expressions, but (in addition to being discouraged altogether) those should be welcome and encouraged to use type annotations. I would be fully supportive of an on-by-default clippy lint that fired when an unannotated const contains too many statements or doesn't mention the outer type name in its final expression. |
On the procedural point, I think it's perfectly valid to raise a concern on an FCP close. As usual, that should probably prompt us to discuss it (rather non-urgently), so... @rustbot labels +I-lang-radar On this example: const WRAPPED_PI: MyStruct<f32> = MyStruct(3.1415_f32); What's your view on the idea that we'd be more likely to want to want to solve that with RHS solutions? That is, something along the lines of, e.g.: const WRAPPED_PI: MyStruct<f32> = _(3.1415); Rather than: const WRAPPED_PI: _ = MyStruct(3.1415_f32); |
That would not work with functions and personally I'd find it extremely confusing. |
Also does not help with arrays |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
I've been persuaded by @scottmcm that if you don't want to write the number in the signature, then you probably don't want callers to rely on the exact number anyway, and so some solution that makes this opaque is probably correct. |
I think we should in simple cases (e.g. if the entire body is a constructor call), but that's not on topic for this issue. |
Allow type inference for
const
orstatic
when the RHS is known.Pre-RFC discussion: #1349
cc #1623
Rendered