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

Tracking issue for RFC 2203, "Constants in array repeat expressions" #49147

Open
Centril opened this Issue Mar 18, 2018 · 13 comments

Comments

Projects
None yet
7 participants
@Centril
Copy link
Contributor

Centril commented Mar 18, 2018

This is a tracking issue for the RFC "Constants in array repeat expressions" (rust-lang/rfcs#2203).

Steps:

Unresolved questions:

None

@F001

This comment has been minimized.

Copy link
Contributor

F001 commented May 4, 2018

I had an investigation on this issue. I think removing below lines can achieve our goals. But I'm not sure whether it is enough.

Rvalue::Repeat(operand, len) => if *len > 1 {
let operand_ty = operand.ty(mir, tcx);
let trait_ref = ty::TraitRef {
def_id: tcx.lang_items().copy_trait().unwrap(),
substs: tcx.mk_substs_trait(operand_ty, &[]),
};

if let Ok(count) = count {
let zero_or_one = count.val.to_raw_bits().map_or(false, |count| count <= 1);
if !zero_or_one {
// For [foo, ..n] where n > 1, `foo` must have
// Copy type:
let lang_item = self.tcx.require_lang_item(lang_items::CopyTraitLangItem);
self.require_type_meets(t, expr.span, traits::RepeatVec, lang_item);
}
}

And another question, given that this change should be guarded by a feature gate, how can we suggest user to enable this feature when a compile error occurs?

I'd appreciate it if somebody can write a mentoring instruction.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented May 4, 2018

See rust-lang/rfcs#2203 (comment) - I don't think we should implement this before the MIR borrowck becomes the default - while it may be possible, it's getting increasingly risky to do such things.
cc @nikomatsakis

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented May 26, 2018

So I just realized that "constant" has been a red herring all along for this problem:
What const-checking has is "value-based reasoning", that is, a value like None::<T> contains no values of T, so T's properties (like Drop or the presence of interior mutability) do not apply to it.

For [expr; n] where we don't know that n <= 1, we've always required typeof(expr): Copy.
The accepted RFC would allow another option which is SomeFormOfConstant(expr).
However, this isn't as general as it could be and it doesn't tie into the Copy condition very well.

One example of a runtime repeated expression we could allow is [Ok::<i32, String>(rand()); n].
Or to expand it a bit, { let x = rand(); [Ok::<i32, String>(x); n] } - this is "obviously fine" because the user could write n of Ok(x), so inline ADT construction is always "inherently copyable".

I propose (a bit late) that we consider a value (dataflow) analysis, Copyable(expr), which works similar to the current value analyses used in const-checking, effectively "recursing" on ADT constructors, and using the following two rules when reaching leaves x of unknown values:

  • typeof(x): Copy (the set of types currently allowed in [x; n])
  • x is already a constant (by naming a constant or through the pre-existing promotion)

Another advantage of this is that it's not limited to [expr; n]: any copy could be satisfied through it!

cc @nikomatsakis @RalfJung

@RalfJung

This comment has been minimized.

Copy link
Member

RalfJung commented May 26, 2018

So I just realized that "constant" has been a red herring all along for this problem:
What const-checking has is "value-based reasoning", that is, a value like None:: contains no values of T, so T's properties (like Drop or the presence of interior mutability) do not apply to it.

Trying to recast what you are saying in my terms, we have Copy and "needs drop" as properties of types, but in fact these are properties of values. T: Copy merely says that all values of type T are copy. And a value is Copy if it can be duplicated because it doesn't have any ownership in it. Most of the time we only care about the type-level Copy because that's all the compiler knows, but e.g. the None value of type Option<T> is Copy for any T. Is that what you are saying?

I think that's a remark I already made somewhere else earlier this year but I have no idea where.^^

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented May 27, 2018

Most of the time we only care about the type-level Copy because that's all the compiler knows, but e.g. the None value of type Option<T> is Copy for any T. Is that what you are saying?

Yes, except I only realized it can apply to all copies at the very end of writing my entire comment, so my reasoning may look quite roundabout. I suspect this might not even be the only place where we can generalize a type-based analysis to a value-based one (like const-checking has).

We could even mix this with polymorphism, by putting "refinement types" through generics.
(but that's riskier because of soundness implications that are harder to understand right now)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented May 28, 2018

@eddyb hmm, interesting thought. So this is basically "doubling down" on the value-based reasoning we use for consts, and applying it here as well? I don't entirely hate that, particularly if we can kind of "reuse" the form of the rule from consts exactly.

@runelabs

This comment has been minimized.

Copy link

runelabs commented May 29, 2018

I ran into the following constraints with trying to use primitive array repeat in struct initialization. It seems there is no way to make a RHS expression with the default value None of an Option for [x; n] without implementing Copy or needing type ascription.
I read the previous open issues on type ascription, but with default() for Option it would be nice to just be able to easily use the default None value for repetition. I am using this for a no_std crate.

enum Wrap { ... }
struct MyStruct<'a> {
  arr: &'a mut [Option<Wrap>]
}
impl<'a> MyStruct<'a> {
  pub fn new(list: &'a mut [Option<Wrap>]) -> Self { Self{ arr: list} } }
}
// works fine, and is very ergonomic
let mut z = MyStruct{ arr: &mut [] }; 
// however the logical follow-up and equally ergonomic &mut [None;N] fails...

// all fine, Copy-trait not needed (*1)
let mut empty1: [Option<Wrap>;11] = Default::default();
let mut a = MyStruct{ arr: &mut empty1 };

// need type ascription, Copy-trait not needed (*2)
let mut empty2 = Default::default():[Option<Wrap>;11];
let mut b = MyStruct::new(&mut empty2);

// need type ascription, Copy-trait for Wrap
let mut empty3 = [Default::default();11]:[Option<Wrap>;11]; // (*3)
let mut empty4 = [Option::default();11]:[Option<Wrap>;11]; // (*4)
let mut empty5 = [None;11]:[Option<Wrap>;11]; // (*5)

// need type ascription, parenthesized expression ... but alas, lifetime fails when (expr)
let mut c = MyStruct{ arr: &mut ([Default::default();11]:[Option<Wrap>;11]) }; // (*6) , also parens expr
let mut d = MyStruct::new( &mut ([Default::default();11]:[Option<Wrap>;11]) ); // (*6b) , also parens expr

// need Copy-trait for Wrap
let mut e = MyStruct{ arr: &mut [Default::default();11] }; // (*7)
let mut f = MyStruct{ arr: &mut [None;11] }; // (*8)
let mut g = MyStruct::new(&mut [None;11]); // (*9)

All the cases from (*1) to (*9) try to accomplish similar initialization. Ideally, shouldn't they all work similarly?

I really want to use (*9), (*8) or (*7) - and not (*1) or (*2) which are not ergonomic.

None seems like a special repetition case not needing Copy. Similarly it is very awkward having arr: [Option<Wrap>;11] and not being able to use [None;11] for initializing it in a structure, but actually having to expand it all like [None,None,None,...]. It also does not feel natural to need type ascription for such basic initialization. Needing to declare const X: T = None; for a [X; N] also does not help in making Rust a more accessible programming language for newcomers. It requires reading up on this special case for Option repetition - instead of what should really just be a simple programming pattern similar to MyStruct{ arr: &mut [] } which is valid, immediately accessible and ergonomic.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented May 29, 2018

@nikomatsakis Indeed, I think we should double down on value reasoning in favor of ergonomics.
In my view, NLL is doing sort of a similar thing, albeit the analysis is relatively more complex.


Needing to declare const X: T = None; for a [X; N] also does not help in making Rust a more accessible programming language for newcomers

This is not required in the accepted RFC, you will be able to just write [None; N] inline.

It requires reading up on this special case for Option repetition -

Option is not special-cased, it just happens that None contains no non-copy data. None would be just as accepted as e.g. Ok(123) (for any Result<_, _> type, including non-copy ones).

As for your ascription questions: you can make these replacements in your code:

  • Wrap and even Option<Wrap> -> _ (inference)
    • always try _ first when trying to specify a type (e.g. Vec<_> before Vec<T>)
    • in let x: _; and expr: _, the _ is redundant (inference works regardless)
  • [expr; n]: [_; n] -> [expr; n] (the type ascription is also redundant here)
  • Default::default(): T -> T::default() (or <T>::default if T isn't a path)
    • e.g. <[_; n]>::default()
Then your code looks like this (click to expand).
enum Wrap { ... }
struct MyStruct<'a> {
  arr: &'a mut [Option<Wrap>]
}
impl<'a> MyStruct<'a> {
  pub fn new(list: &'a mut [Option<Wrap>]) -> Self { Self{ arr: list} } }
}
// works fine, and is very ergonomic
let mut z = MyStruct{ arr: &mut [] }; 
// however the logical follow-up and equally ergonomic &mut [None;N] fails...

// all fine, Copy-trait not needed (*1)
let mut empty1: [_;11] = Default::default();
let mut a = MyStruct{ arr: &mut empty1 };

// need type ascription, Copy-trait not needed (*2)
let mut empty2 = <[_;11]>::default();
let mut b = MyStruct::new(&mut empty2);

// need type ascription, Copy-trait for Wrap
let mut empty3 = [Default::default();11]; // (*3)
let mut empty4 = [Option::default();11]; // (*4)
let mut empty5 = [None;11]; // (*5)

// need type ascription, parenthesized expression ... but alas, lifetime fails when (expr)
let mut c = MyStruct{ arr: &mut [Default::default();11] }; // (*6) , also parens expr
let mut d = MyStruct::new( &mut [Default::default();11] ); // (*6b) , also parens expr

// need Copy-trait for Wrap
let mut e = MyStruct{ arr: &mut [Default::default();11] }; // (*7)
let mut f = MyStruct{ arr: &mut [None;11] }; // (*8)
let mut g = MyStruct::new(&mut [None;11]); // (*9)

Further notes from looking at the result of that:

  • [Default::default(); 11] and [Option::default(); 11] are no better than [None; 11]
  • <[_; 11]>::default() works even today because it calls Option::default 11 times from the implementation of Default for [T; 11], so it never needs to copy an Option<Wrap>

@runelabs I hope I've clarified a few things and/or resolved most of your concerns.

@runelabs

This comment has been minimized.

Copy link

runelabs commented May 29, 2018

@eddyb Thanks, I hadn't thought of <[_; 11]>::default() . That is the "turbofish" notation?
It wasn't immediately intuitive to me as a replacement. The compiler suggested type inference when I was exploring options earlier, which led to looking at type ascription although it seemed unnecessary. As long as the [None;11] will be working with this RFC, I am a happy camper. From the RFC it seemed like it had to be a const declaration beforehand. Good it wasn't so 😄

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented May 29, 2018

<T>::foo is "type-qualified path" syntax, which T::foo is a shorthand for (if T is not a trait).
Usually only foo::<T> is referred to as turbofish, the former would be more "reverse turbofish".

@eddyb eddyb added the S-blocked label Aug 24, 2018

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Aug 24, 2018

Is the S-blocked label the right way to do this? I kind of want a "needs NLL" but not "fixed by NLL" label (cc @nikomatsakis).

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Dec 21, 2018

@eddyb hmm maybe we could/should just rename "fixed by NLL" to "needs NLL", which is ... strictly more general?

(Or is that label not a true generalization? I'm still musing over the english semantics here. I guess "fixed by NLL" implies that NLL is sufficient but not (necessarily) necessary, while "needs NLL" implies NLL is necessary but not (necessarily) sufficient...)

  • I can't believe just wrote "necessarily necessary"
@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Dec 21, 2018

Discussed briefly on Zulip -- would we want to experiment with this on 2018 only to begin with?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment