Why are constant declarations in a function 'in scope' before their declaration?


#1

Whilst writing some test code today, I came across an oddity: A const declaration in my function was referencable before it’s declaration, viz:-

#[test]
fn something() {
    let a: usize = 4;
    assert_eq!(a, b);

    const b: usize = 5;
}

Is this intentional? It certainly violates my ‘this is what I would expect’, which is that a const is only in scope after declaration, a bit like a final variable in Java, etc. This behaviour somewhat matches JavaScript, where all manner of bugs over the years have happened because of it… Of course, const should be available logically before its declaration either in a trait, an impl or a module; but that’s expected.

I’ve created a playground link.

Is there a good reason or advantage to the current behaviour?


#2

const ... is not a statement, it’s an item and items visibility inside function bodies is consistent with all other scopes.


#3

Thanks for the explanation as to why. I do feel it violates the principal of least surprise. What I’m more interested in knowing is whether there’s a pragmatic advantage to this behaviour for the programmer. In my particular case, I spent 15 mins or so tracking down a bug I’d accidentally introduced. From the point of view of readability, having an item that requires one to potentially read from either direction in a function is, well, absurd. Rust is hard enough as it is for the mainstream. Total consistency might make sense to some, but a language is a tool with a means to an end - writing programs that work well, are straightforward to maintain over many years and can be understood with ease. A good program should be like a good book; each section has one purpose, and each section is a summary of those logically beneath it.


#4

It’s not clear why you’d put a const in a function like this. Consider that variable bindings are immutable by default (and I believe this is being pointed out in the introductory materials) so

let b = 5;

should be just as good.

Additionally const b produces a warning suggesting you name it B to avoid confusing it with a variable.

Introducing item visibility inconsistencies also violates the same principle and makes a language more complex.


#5

Maby a clipy lint?


#6

Thank you for your reply. Immutable variable bindings by default are one of the big attractions of Rust for me, and I use them extensively. However, immutable variable bindings can be shadowed, and const are not; one definition, one value. Likewise it prevents one creating a variable binding to something of the same name. This is good and bad; it depends on what one is trying to achieve.

Why would one want to use a const in a function? In this case, this was a single unit test. The scope of some of the test data was that test case. It was invariant. It was also, like most good test data, not something that should be the result of complex calculation, time of day, etc - ie always reproducible and simple to understand. const seemed to express that purpose.

Regarding const b producing a warning - I am aware that it does with the default warnings rustc uses, but the code I’ve written up above was intended as a simplification. As it happens, one can’t rely on those. As Rust develops, house styles will evolve - and the current Pythonesque style won’t suit everyone (indeed, I’d go further and suggest it’s actually an anti-style for some people, due to the way they read and process written information - myself included; an area of study that I’d like to do a paper on one day if the time and budget ever allow).

I can’t agree with the first part of your final point, although I can understand the second part; it might make it slightly more complex. Many real written languages are extremely, and often inconsistent - English is often the canonical example - yet they allow the expression and communication of complex ideas and suitable nuances with great speed. I’ve laid out my arguments; I don’t really have much more to say to persuade you. I appreciate your comments.


#7

Having consts inside functions is quite useful.


#8

Actually, const can be shadowed:

fn foo() {
    const F: i32 = 92;
    {
        const F: i32 = 62;
        println!("{}", F);
    }
}