Pre-RFC: Untyped constants


#21

This is idiomatic use of const (i.e. giving a name to a constant value) and what you’re describing sounds like static (i.e. placing a constant value in a static memory location).


#22

I don’t think it’s idiomatic to use const ‘as much as possible’ within functions; I mean, there are use cases for it, but it sounded like @johnthagen wanted to use it in cases where let would be more idiomatic.

And yeah, I didn’t want to complicate things further with const versus static, since usually the behavior is the same.


#23

Oops, I hadn’t seen the actual reasoning for using const, which AFAIK is invalid, I agree with you there.


#24

Yes, your intuition was correct. It’s something I’ve been struggling to figure out how to do correctly and consistently. The only definitive response I had gotten up to this point was

https://users.rust-lang.org/t/when-should-const-be-used-within-a-function/8848/6?u=johnthagen

My rationale was wanting to default to the most restrictive type possible (similar to defaulting to immutable bindings), so it was a sort of “static by default”. But for all of the reasons you’ve pointed out, I am going to stop doing that.

Would this be a good summary for the idiomatic rules for const?

  1. Use for immutable constants whose scope is larger than a function.
  2. Use for large-sized* immutable constants declared within functions.

Where large-sized means a type takes significantly larger memory storage than an integer/pointer type.


#25

IMO an IntegerLiteral trait would be nice.

It means you can do this to emulate “constants”:

fn universal_constant<I: IntegerLiteral>() -> I { 42 }

Could also replace fn with const fn too.


#26

Wouldn’t some kind of “untyped number” (<- const only) type be a alternative solution?

e.g. const FOO: UntypedNumber = 123456;

Where the constant FOO is interpreted dependent on it’s context. And a error(warning?) is thrown if the given typed number does not fit into the given numeric type.

Wrt. to backward compatibility and name collision UntypedNumber would be type in std marked with #[lang_item="untyped_number"] (also it should be zero sized with a single private zero sized field to prevent usage elsewhere, maybe also warning if used in generics etc.).

Not that I intentionally used “number” not integer, there is no reason to limit this functionally to integers e.g. let x: f32 = FOO; should then be equivilant to let x: f32 = 123456f32 and FOO could aso be const FOO: UntypedNumber = 12.4; making it usable with f32/f64 and possible other native number types if they are added to rust.

Additionally std might provide a simple (const) fn to hint the type (as is not enough as as would also be used for casting. E.g. let x = FOO as u8; is ambiguous between Error 1234 does not fit into u8 and 1234<?> as u8, defaulting to the former one would be unnecessary error prone. Basically a (#[inline(always)] const) fn typed<T>(x: T) -> T { x } (not the ( .. ) is because I’m not sure about #[inline(always)] and const is not stable, also the combination of both makes little sense :slight_smile:)


TL;DR:

in (e.g.) std/const/lib.rs

#[lang_item = "untyped_number" ]
pub struct UntypedNumber {
    _no_construction: ()
}

pub const fn typed<T>(x: T) { x }

in example

use std::const::{UntypedNumber, typed};

const FOO: UntypedNumber = 1234;
const BAR: UntypedNumber =12.4; //ok, through works only with f32/f64 etc.

fn main() {
    // for simplicity I will only use `let var =` and `let var: type =` state ments
    // for all cases where the type of `var` is unknown and know through other
    // means respectively
    let a: u32 = FOO; //FOO equiv. 1234u32
    let b: f32 = FOO;  //FOO equiv. 1234f32
    let c = typed::<f32>(FOO); //equiv. to `typed::<f32>(1234f32)`
    let a = FOO; //error can't resolve type
    let a: u8 = FOO; //error u8 can only contain numbers in range [0;255] got 1234
    let a = FOO as u8; //error can't resolve type, abiguity between typing and casting
    let a = FOO as u32; //aslo? error can't resolve type, abiguity between typing and casting or not??
    let a = typed::<u32>(FOO) as u8; //equiv. to `1234u32 as u8`
}

not that typed is no “magic” at all just a (additional) way to hint the type to the compiler (it’s not necessary needed, if it is wanted I do not know).

Note that currently typed::<f32>(12) does not compile as 12 is a {integer}. Through it would be fine with FOO as, given the type hint of T=f32, FOO would be equivalent to “1234 f32”, and not just “1234”.

Question 1: is there any non number usage for “untyped” constants

Question 2: is there any case where a typed::<...>(FOO) should not error if the number does not fit into the type (e.g. warn instead and use as ...). Also wrt. float we would have to take the closest actual representable number (else 0.1 won’t work), but when(if at all) do/should we error/warn that f32’s precision is to low for given number?

EDIT: I just noticed that I only read from @retep998 post (nr. 14, 6.3.2017) onward and a similar Idea had already been mentioned.


#27

PS:

I just noticed that I posted on this thread before, and that my opinion has slightly changed since then.

I no longer believe that something like const x = 1234; as it would mess with type interference for constants (like mentioned by @tbu before).

Also I wrote before that a literal types can have some problems in combination with literals/numbers as type-parameters (e.g. <n:UINT> for a Array type). I do no longer believe this. In the worst case the usage as generic parameter could simple be forbidden for a type tagged with lang_item="untyped_number". Nevertheless this depends on how literals as type-parameters are implemented, if they are limited to specific types (uXX, &'static str??) this is no problem anyway. In that case UntypedNumber can be resolved as such type (with all the “error cases” seen before e.g. number can’t be represented with given precision).
If “general” const values (inclusive const structs) can be used it is a bit more tricky, but still would be fine with the rules for resolving FOO’s type indirectly mentioned above. Basically what I proposed in previous post can be seen very similar to using a macro/text replacement, expect that the “right” suffix (e.g. f32 in 1f32) is added to the expanded number. Which should resolve any question about ambiguity and casting in a future prove way (<- I think but might be wrong :wink:) .