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

Const vs static #246

Merged
merged 1 commit into from
Oct 2, 2014
Merged

Conversation

nikomatsakis
Copy link
Contributor

A proposal to extend statics/constants so that static always declares a global variable (memory location) with an address and const declares global values. const is what we expect users to use most of the time, static is only intended to be used in narrow circumstances.

Clarify taking addresses of globals in globals
@alexcrichton
Copy link
Member

cc #177, #228

We have been wrestling with the best way to represent globals for some
times. There are number of interrelated issues:

- *Significant addresses and inlining:* For optimization purposes, it

Choose a reason for hiding this comment

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

Insignificant addresses (unnamed_addr) don't have any implications on mutability. Marking two mutable globals with the same initializer as unnamed_addr does not mean the compiler can merge them without first proving that they are read-only/write-only or some other invariant guaranteeing that they are not used independently.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@thestinger You seem to be responding to some text other than the one the comment is attached to, since that text doesn't mention mutability. Can you clarify what you mean? (That is, what concrete change does that imply for the proposal? Is there a restriction you think we ought to lift?)

However, I also want to point out that the semantics we choose here are not governed by what LLVM does -- that is, we could decide to inline or duplicate constants more aggressively (or less aggressively) than LLVM permits itself to do.

Choose a reason for hiding this comment

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

The address being insignificant only means that converting it to an integer and performing comparisons isn't meaningful. A constant can be inlined without giving it an insignificant address by using available_externally and a mutable global can be given an insignificant address without breaking anything.

@glaebhoerl
Copy link
Contributor

In general this seems like the right approach to me!

Obvious bikeshed is obvious: we currently use the const keyword in *const pointers. Use of the same const keyword in one context, where it is explicitly intended to match the C meaning, as well as in another context (proposed here) where it means something related but significantly different (in particular, it's not the complement of mut), is likely to be confusing for C programmers (and possibly Rust programmers).

}

This would allow us to make the `value` field on `UnsafeCell` private,
among other things.
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 similar to C++'s constexpr. See also my related thoughts here (s/static/const/g). In particular C++ also originally started out, in C++11, with only allowing constexpr functions to consist of a single expression (although with recursion); then in C++14 they greatly generalized this to allow various imperative constructs as well, as long as they can be computed at compile time. (I haven't looked at what the precise rules are; they're probably complicated.) In Rust, though, we have the advantage that all code barring unsafe has a precisely-defined meaning. This means that all Rust code which doesn't involve unsafe can be safely evaluated at compile time! (We can't prove termination, but I don't think we make any attempt to prove it for e.g. impl lookup or macro expansion either.)

@nikomatsakis
Copy link
Contributor Author

@glaebhoerl true, but in practice "all rust code that doesn't use unsafe" is a fairly small subset, given that library things like Vec and Box are implemented unsafely. Anyway, this RFC purposefully left something like constexpr for "future work" precisely because it poses so many exciting challenges. That said, I think that advanced cases are subsumed by syntax extensions, so we may be able to get away with a simplistic constexpr variant that is quite limited.

an `UnsafeCell` in its interior, the compiler may place it in
read-only memory, but otherwise it must be placed in mutable memory.

`mut` statics may have any type. All access is considered unsafe.

Choose a reason for hiding this comment

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

Is a "static mut" of type T any different from a non-mut static of type UnsafeCell in this proposal? (other than syntax)

It seems not, and in this case it seems redundant to provide static muts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On Thu, Sep 18, 2014 at 03:05:03PM -0700, bill-myers wrote:

Is a "static mut" of type T any different from a non-mut static of type UnsafeCell in this proposal? (other than syntax)
...
It seems not, and in this case it seems redundant to provide static muts.

I believe you are correct that there is little added value. They are
there for convenience, for analogy with "let mut", primarily, and
perhaps partly for interfacing with C code (that is, extern static
mut?). The latter point seems important, although the memory layout of
Unsafe<T> and T are identical, so perhaps it is not. That said, it
makes the proposal simpler to exclude them. This is roughly what PR
#228 proposed (well, not quite, but very similar).

@Valloric
Copy link

This RFC looks like a great idea.

@ftxqxd
Copy link
Contributor

ftxqxd commented Sep 19, 2014

I wonder if consts could be untyped like Go’s constants. That is, perhaps there should be two types of consts:

  • const foo: Type = value; foo is equivalent to value: Type (pretending we have type ascription)
  • const foo = value; foo is equivalent to value

Type inference would work out the type of an untyped const at the use site, whereas typed consts would have their types specified directly at their definition. This would make untyped constants more like C #define constants (although much nicer). This, combined with arbitrary-precision floating point constant expressions (so that constant floating point expressions like 1.2 * 3.4 would be evaluated as arbitrary-precision numbers at compile time and converted into a single literal like 4.08) would not only remove the need for the std::f32::consts/std::f64::conts duplication but also remove all of the constants that could easily be derived from others like FRAC_1_PI and so on.

@Ericson2314
Copy link
Contributor

@p1start Pretty sure that can be accomplished with a macro that takes no arguments.

@Kimundi
Copy link
Member

Kimundi commented Sep 20, 2014

This seems like a nice clan separation of concerns between truly constant rvalues on the one hand, and global variable lvalues on the other hand.

One aspect I didn't see mentioned in the RFC is the case of associated statics: Their proposed semantic only fits const well, not static, so separating them makes their semantic more clear.

I think the rules around borrowing static and static mut could be made a bit more consistent with let and in regard to the Share bound:

&foo static foo: T static mut foo: T
T: Share safe safe
T: !Share unsafe unsafe
&mut foo static foo: T static mut foo: T
T: Share forbidden unsafe
T: !Share forbidden unsafe

That is, static would behave like let in regard to borrowing and what types are valid (inherited mutability, and all), but be generally unsafe. Only exception being taking aliasing borrows to a Share type: Those can be considered safe. (It would still be possible to mess things up non-locally with unsafe code by mutably borrowing a static mut that has already been borrowed, but thats the job of the programmer to prevent by suitable visibility restrictions)

@brendanzab
Copy link
Member

I like this. It neatly steps around the ridiculous const mut that we were so worried about when we switched to static when introducing mutable globals. The exciting thing is the possibilities for CTFE in the future. I think that the extra keyword is fine.

have generic parameters. For example, the following constant is legal:

struct WrappedOption<T> { value: Option<T> }
const NONE<T> = WrappedOption { value: None }
Copy link
Member

Choose a reason for hiding this comment

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

Curious! I had something similar in rust-lang/rust#10798 (I'm kinda surprised the issue is still open).

@glaebhoerl
Copy link
Contributor

I think that the extra keyword is fine.

(As I noted above we do already have the const keyword...)

@mahkoh
Copy link
Contributor

mahkoh commented Sep 21, 2014

Not super happy with the way &CONSTANT works.

Constants are considered rvalues. Therefore, taking the address of a constant actually creates a spot on the local stack

let x = &module::UPPER_SNAKE_CASE;

it's not clear what the lifetime of the reference is. If we have a static then it's 'static and otherwise it's the lifetime of the current block. Afaik, taking a reference of an identifier always creates a reference with a lifetime as long as the lifetime of the identifier (unless the lifetime of the reference is artificially restricted.)

As a possible extension, it is perfectly reasonable for constants to have generic parameters. For example, the following constant is legal:

The word const along with this, and @P1start's suggestion above, and the way const is treated in Go gives me the feeling that constants are evaluated by simply replacing the token by the right hand side of the definition. But this is not true (see below.)

Note that this makes no sense for a static variable, which represents a memory location and hence must have a concrete type.

I don't see why this wouldn't work. Why not reserve the memory after monomorphization?

Constants are generally inlined into the stack frame from which they are referenced, but in a static context there is no stack frame.

const X = 1i;
const Y = &X; // static lifetime
let z = &X; // block lifetime

This sounds dangerous. Consider the following alternative:

  1. You cannot take a &mut of a constant.
  2. Taking a & of a constant always creates a static immutable variable and all such references point to the same memory address.
const BAR<T> = &FOO<T>;

How would this even work if you can't create such a static?

@Kimundi
Copy link
Member

Kimundi commented Sep 22, 2014

In todays Rust, let x = &FOO; can already mean that FOO is rvalue: See enum variants and unit structs.

const would just generalize their semantic to an explicit construct.

@mahkoh
Copy link
Contributor

mahkoh commented Sep 22, 2014

@Kimundi Those are not written in UPPER_SNAKE_CASE.

@Kimundi
Copy link
Member

Kimundi commented Sep 22, 2014

The casing style is just convention. Granted, useful convention to see at a glance how something will likely behave, which is what you're meaning, but nevertheless the language does not enforce that unit structs and enum variants are always written in camel caps, nor does it enforce that constants are upper snake case.

Besides, I think not knowing whether a reference to a constant/static has 'static or non-'static lifetime will not be that big of an issue in practice:

Lets assume we switch to separate const/static. Then almost all uses of static today will become const, which means in written code &FOO can generally be assumed to have local lifetime. If its actually a static, then there is no semantic change: Any code that is correct with a local lifetime is also correct with 'static, and hence there is no problem in understanding the code.

@nikomatsakis
Copy link
Contributor Author

On Sun, Sep 21, 2014 at 09:19:10AM -0700, mahkoh wrote:

Not super happy with the way &CONSTANT works.

Thanks for your comment. Let me try to lay out what led me here.

Constants are considered rvalues. Therefore, taking the address of a constant actually creates a spot on the local stack

let x = &module::UPPER_SNAKE_CASE;

it's not clear what the lifetime of the reference is. If we have a static then it's 'static and otherwise it's the lifetime of the current block. Afaik, taking a reference of an identifier always creates a reference with a lifetime as long as the lifetime of the identifier (unless the lifetime of the reference is artificially restricted.)

Actually, I think the lifetime would only be less clear if we had more
fine-grained rules. The current rules were deliberately chosen to be
simple so as to aid predictability. If you want a pointer with static
lifetime, you shouldn't use let but rather another const (which in
turn implies some limitations around interior mutability).

Interior mutability is the key point here. The main reason to make
constants values and not memory addresses is to make it trivial to
support interior mutability within constants, while minimizing rules
like "if there is interior mutability, the lifetime is this, otherwise
it is that".

Note that this makes no sense for a static variable, which represents a memory location and hence must have a concrete type.

I don't see why this wouldn't work. Why not reserve the memory after monomorphization?

It's not that we can't do it; it just means that a single static
corresponds to potentially many memory locations, with the compiler
free to duplicate/consolidate as it feels free. But then this starts
to run into weird semantics around interior mutability (because that
makes it very visible which copies are consolidated and which are
not). Hence you want to forbid interior mutability and require static mut for that case -- but now we're kind of back where we
started. What about generic static muts? Keep following that road and
you wind up where we are today.

The const design largely sidesteps this question by segregating out
values and memory locations. Note that I said largely, there are
still a few finnicky rules around interior mutability, but they are
minimized.

This sounds dangerous.

I am not sure what you mean by "dangerous". I interpret it as "unsafe"
-- i.e., likely to lead to crashes, and I disagree. Maybe you just
mean "confusing"? If so, I am sympathetic -- this turns out to be a
surprisingly complicated topic.

Consider the following alternative:

  1. You cannot take a &mut of a constant.
  2. Taking a & of a constant always creates a static immutable variable and all such references point to the same memory address.
const BAR<T> = &FOO<T>;

How would this even work if you can't create such a static?

It's not that we can't do it, but that awkward questions get raised.
In this case, this is only legal if FOO<T> has no interior
mutabilty, in which case it's reasonable for the compiler to create as
many (or as few) copies as it feels like.

Note that if FOO does have interior mutability, you can still create
static or static mut for each type that you happen to need:

static BAR = &Foo<int>;

@glaebhoerl
Copy link
Contributor

Just so the option is considered: what if we don't add the

const MY_CONST: &'static Foo = &OTHER_CONST;

magic, and require manually writing the intermediate static:

static MY_STATIC: Foo = OTHER_CONST;
const MY_CONST: &'static Foo = &MY_STATIC;

instead?

Would this meaningfully reduce the amount of complication around interior mutability? If so, how annoying would it be? (If not, it's obviously not worth thinking about further.)

@mahkoh
Copy link
Contributor

mahkoh commented Sep 23, 2014

@nikomatsakis:

I am not sure what you mean by "dangerous". I interpret it as "unsafe" -- i.e., likely to lead to crashes, and I disagree.

I was thinking about cases where a wrong lifetime can cause crashes, e.g., transmute and FFI. But after @Kimundi's comment I think this is not really an issue.

@nikomatsakis
Copy link
Contributor Author

On Tue, Sep 23, 2014 at 05:30:34AM -0700, Gábor Lehel wrote:

Would this meaningfully reduce the amount of complication around interior mutability? If so, how annoying would it be? (If not, it's obviously not worth thinking about further.)

In some sense yes. I think the only rule that is directly tied to
interior mutability is that one. The other related rule is that
statics must meet Sync, so that access to them is safe. (Though the
compiler will still reason about interior mutability to decide whether
a static can be placed in read-only memory.) This was in fact my
original plan.

However, we changed the plan because we wanted to ensure that people
only need to know about static variables global, shared mutable state
(e.g. a shared atomic counter or lock), which I consider rather
unusual. I believe that for all other use cases const is sufficient.

@alexcrichton
Copy link
Member

We've decided to accept this at the last meeting:

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Oct 9, 2014
This change is an implementation of [RFC 69][rfc] which adds a third kind of
global to the language, `const`. This global is most similar to what the old
`static` was, and if you're unsure about what to use then you should use a
`const`.

The semantics of these three kinds of globals are:

* A `const` does not represent a memory location, but only a value. Constants
  are translated as rvalues, which means that their values are directly inlined
  at usage location (similar to a #define in C/C++). Constant values are, well,
  constant, and can not be modified. Any "modification" is actually a
  modification to a local value on the stack rather than the actual constant
  itself.

  Almost all values are allowed inside constants, whether they have interior
  mutability or not. There are a few minor restrictions listed in the RFC, but
  they should in general not come up too often.

* A `static` now always represents a memory location (unconditionally). Any
  references to the same `static` are actually a reference to the same memory
  location. Only values whose types ascribe to `Sync` are allowed in a `static`.
  This restriction is in place because many threads may access a `static`
  concurrently. Lifting this restriction (and allowing unsafe access) is a
  future extension not implemented at this time.

* A `static mut` continues to always represent a memory location. All references
  to a `static mut` continue to be `unsafe`.

This is a large breaking change, and many programs will need to be updated
accordingly. A summary of the breaking changes is:

* Statics may no longer be used in patterns. Statics now always represent a
  memory location, which can sometimes be modified. To fix code, repurpose the
  matched-on-`static` to a `const`.

      static FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

  change this code to:

      const FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

* Statics may no longer refer to other statics by value. Due to statics being
  able to change at runtime, allowing them to reference one another could
  possibly lead to confusing semantics. If you are in this situation, use a
  constant initializer instead. Note, however, that statics may reference other
  statics by address, however.

* Statics may no longer be used in constant expressions, such as array lengths.
  This is due to the same restrictions as listed above. Use a `const` instead.

[breaking-change]

[rfc]: rust-lang/rfcs#246
bors added a commit to rust-lang/rust that referenced this pull request Oct 10, 2014
This change is an implementation of [RFC 69][rfc] which adds a third kind of
global to the language, `const`. This global is most similar to what the old
`static` was, and if you're unsure about what to use then you should use a
`const`.

The semantics of these three kinds of globals are:

* A `const` does not represent a memory location, but only a value. Constants
  are translated as rvalues, which means that their values are directly inlined
  at usage location (similar to a #define in C/C++). Constant values are, well,
  constant, and can not be modified. Any "modification" is actually a
  modification to a local value on the stack rather than the actual constant
  itself.

  Almost all values are allowed inside constants, whether they have interior
  mutability or not. There are a few minor restrictions listed in the RFC, but
  they should in general not come up too often.

* A `static` now always represents a memory location (unconditionally). Any
  references to the same `static` are actually a reference to the same memory
  location. Only values whose types ascribe to `Sync` are allowed in a `static`.
  This restriction is in place because many threads may access a `static`
  concurrently. Lifting this restriction (and allowing unsafe access) is a
  future extension not implemented at this time.

* A `static mut` continues to always represent a memory location. All references
  to a `static mut` continue to be `unsafe`.

This is a large breaking change, and many programs will need to be updated
accordingly. A summary of the breaking changes is:

* Statics may no longer be used in patterns. Statics now always represent a
  memory location, which can sometimes be modified. To fix code, repurpose the
  matched-on-`static` to a `const`.

      static FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

  change this code to:

      const FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

* Statics may no longer refer to other statics by value. Due to statics being
  able to change at runtime, allowing them to reference one another could
  possibly lead to confusing semantics. If you are in this situation, use a
  constant initializer instead. Note, however, that statics may reference other
  statics by address, however.

* Statics may no longer be used in constant expressions, such as array lengths.
  This is due to the same restrictions as listed above. Use a `const` instead.

[breaking-change]
Closes #17718 

[rfc]: rust-lang/rfcs#246
meqif added a commit to meqif/rust-utp that referenced this pull request Oct 11, 2014
Change according to Rust RFC 246 [1], implemented in [2].

[1] rust-lang/rfcs#246
[2] rust-lang/rust@f9fc49c
genbattle added a commit to genbattle/nanovg-rs that referenced this pull request Oct 14, 2014
This was a recent change integrated into the rust compiler as per RFC #246 rust-lang/rfcs#246
huonw pushed a commit to rust-random/rand that referenced this pull request Feb 3, 2015
This change is an implementation of [RFC 69][rfc] which adds a third kind of
global to the language, `const`. This global is most similar to what the old
`static` was, and if you're unsure about what to use then you should use a
`const`.

The semantics of these three kinds of globals are:

* A `const` does not represent a memory location, but only a value. Constants
  are translated as rvalues, which means that their values are directly inlined
  at usage location (similar to a #define in C/C++). Constant values are, well,
  constant, and can not be modified. Any "modification" is actually a
  modification to a local value on the stack rather than the actual constant
  itself.

  Almost all values are allowed inside constants, whether they have interior
  mutability or not. There are a few minor restrictions listed in the RFC, but
  they should in general not come up too often.

* A `static` now always represents a memory location (unconditionally). Any
  references to the same `static` are actually a reference to the same memory
  location. Only values whose types ascribe to `Sync` are allowed in a `static`.
  This restriction is in place because many threads may access a `static`
  concurrently. Lifting this restriction (and allowing unsafe access) is a
  future extension not implemented at this time.

* A `static mut` continues to always represent a memory location. All references
  to a `static mut` continue to be `unsafe`.

This is a large breaking change, and many programs will need to be updated
accordingly. A summary of the breaking changes is:

* Statics may no longer be used in patterns. Statics now always represent a
  memory location, which can sometimes be modified. To fix code, repurpose the
  matched-on-`static` to a `const`.

      static FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

  change this code to:

      const FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

* Statics may no longer refer to other statics by value. Due to statics being
  able to change at runtime, allowing them to reference one another could
  possibly lead to confusing semantics. If you are in this situation, use a
  constant initializer instead. Note, however, that statics may reference other
  statics by address, however.

* Statics may no longer be used in constant expressions, such as array lengths.
  This is due to the same restrictions as listed above. Use a `const` instead.

[breaking-change]
Closes #17718 

[rfc]: rust-lang/rfcs#246
@Centril Centril added A-const Proposals relating to const items A-static Proposals relating to static items. labels Nov 23, 2018
gnzlbg pushed a commit to rust-lang/libtest that referenced this pull request Mar 2, 2019
This change is an implementation of [RFC 69][rfc] which adds a third kind of
global to the language, `const`. This global is most similar to what the old
`static` was, and if you're unsure about what to use then you should use a
`const`.

The semantics of these three kinds of globals are:

* A `const` does not represent a memory location, but only a value. Constants
  are translated as rvalues, which means that their values are directly inlined
  at usage location (similar to a #define in C/C++). Constant values are, well,
  constant, and can not be modified. Any "modification" is actually a
  modification to a local value on the stack rather than the actual constant
  itself.

  Almost all values are allowed inside constants, whether they have interior
  mutability or not. There are a few minor restrictions listed in the RFC, but
  they should in general not come up too often.

* A `static` now always represents a memory location (unconditionally). Any
  references to the same `static` are actually a reference to the same memory
  location. Only values whose types ascribe to `Sync` are allowed in a `static`.
  This restriction is in place because many threads may access a `static`
  concurrently. Lifting this restriction (and allowing unsafe access) is a
  future extension not implemented at this time.

* A `static mut` continues to always represent a memory location. All references
  to a `static mut` continue to be `unsafe`.

This is a large breaking change, and many programs will need to be updated
accordingly. A summary of the breaking changes is:

* Statics may no longer be used in patterns. Statics now always represent a
  memory location, which can sometimes be modified. To fix code, repurpose the
  matched-on-`static` to a `const`.

      static FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

  change this code to:

      const FOO: uint = 4;
      match n {
          FOO => { /* ... */ }
          _ => { /* ... */ }
      }

* Statics may no longer refer to other statics by value. Due to statics being
  able to change at runtime, allowing them to reference one another could
  possibly lead to confusing semantics. If you are in this situation, use a
  constant initializer instead. Note, however, that statics may reference other
  statics by address, however.

* Statics may no longer be used in constant expressions, such as array lengths.
  This is due to the same restrictions as listed above. Use a `const` instead.

[breaking-change]
Closes #17718 

[rfc]: rust-lang/rfcs#246
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const Proposals relating to const items A-static Proposals relating to static items.
Projects
None yet
Development

Successfully merging this pull request may close these issues.