Ключевое слово const


О статье

Здесь будет рассмотрено использование ключевого слова const.

С помощью этого слова создаётся константа. Она
«живёт в течении всего времени работы программы. А именно, у них вообще нет определённого адреса в памяти. Это потому, что они встраиваются (inline) в каждое место, где есть их использование. По этой причине ссылки на одну и ту же постоянную не обязаны указывать на один и тот же адрес в памяти».
Давайте создадим её:

const N = 4;      // N: i32
fn main() {
	println!("N = {}",N);
}


И вывод будет таков:

error: expected `:`, found `=`
const N = 4;

~~~~~~^
Это означает, что когда мы объявляем константу с помощью ключевого слова const, то мы должны обязательно указать тип константы! Исправляем:

const N: i32 = 4;


Выход:

N = 4

И всё в порядке. ОК. А что, если мы её переопределим? Пробуем:

const N: i32 = 4;
const N: i32 = 5;

fn main() {
	println!("N = {}",N);
}


Выход:

error: duplicate definition of value `N` [E0428]

Так значит, их нельзя переопределять как переменные! Хм… А что, если мы напишем так:

const N: i32 = 4;
fn main() {
    println!("N = {}",N);
    const N: i32 = 8;
    println!("N = {}",N);
}


Выход:

warning: constant item is never used: `N`, #[warn(dead_code)] on by default
const N: i32 = 4;
^~~~~~~~~~~~~~~~~


Странно. Насколько я понимаю, это значит, что если в функции определена другая константа, то наша первая игнорируется. Обязаны проверить:

const N: i32 = 4;
fn main() {
    println!("N = {}",N);
    const N: i32 = 8;
    println!("N = {}\n",N);
    other();
}

fn other() {
    println!("N = {}",N);
}


Выход:

N = 8
N = 8

N = 4


Ага! Я был прав. Видите: первая константа игнорируется, т.к. в функции main() определена другая с таким же именем, а в функции other() она работает. А если так:

const N: i32 = 4;
fn main() {
    println!("N = {}",N);
    const M: i32 = 8;
    println!("M = {}\n",M);
    other();
}

fn other() {
    println!("N = {}",N);
}


То выход будет таким:

N = 4
M = 8

N = 4


И пожалуй ещё один эксперимент:

fn main() {
    println!("N = {}",N);
    const N: i32 = 8;
    const N: i32 = 9;
    println!("N = {}\n",N);
}


Выход:

error: duplicate definition of value `N` [E0428]
const N: i32 = 9;
^~~~~~~~~~~~~~~~~


Следовательно, делаю вывод, что в одной области видимости не может быть двух констант с одним и тем же именем! И ещё пример:

fn main() {
    println!("N = {}",N);
    const N: i32 = 8;
	{
    	const N: i32 = 9;
	}   
    println!("N = {}\n",N);
}


Если у вас добросовестный компилятор, то он вам выплюнет:

warning: constant item is never used: `N`, #[warn(dead_code)] on by default
const N: i32 = 9;
^~~~~~~~~~~~~~~~~


Очень интересно. Надо всё проверить:

fn main() {
    println!("N = {}",N);
    const N: i32 = 8;
	{
    	println!("N = {}\n",N);
	}   
    println!("N = {}\n",N);
}


Выход:

N = 8
N = 8

N = 8


А если так:

fn main() {
    println!("N = {}",N);
	{
    	const N: i32 = 8;
    	println!("N = {}\n",N);
	}   
    println!("N = {}\n",N);
}


Выход:

error: unresolved name `N` [E0425]
println!(«N = {}»,N);
~~~~~~~~~~~~^
...


И последний пример:

const N: i32 = 3;

fn main() {
    println!("N = {}",N);
	{
    	const N: i32 = 8;
    	println!("N = {}\n",N);
	}   
    println!("N = {}\n",N);
}


Выход:

N = 3
N = 8

N = 3


Значит, если в функции есть константа, то она перекрывает константу с таким же именем, находящуюся, так сказать, «уровнем выше»(под «уровнем» я подразумеваю область видимости). А если константы имеют разные имена, то константа «уровнем выше» распространяется ещё и на «нижние уровни» пока не встретится константа с таким же именем (надеюсь, вы понимаете мою мысль...). Мы с вами в этом убедились (смотри вверх).

И менять их значение тоже нельзя:

fn main() {
    println!("N = {}",N);
	{
    	const mut N: i32 = 8;
    	println!("N = {}\n",N);
	}   
    println!("N = {}\n",N);
}


Выход:

error: const globals cannot be mutable
const mut N: i32 = 8;
~~~~~^~~


Наверное, потому, что
«у них вообще нет определённого адреса в памяти. Это потому, что они встраиваются (inline) в каждое место, где есть их использование».


А если взять адреса?

const N: i32 = 3;

fn main() {
    println!("{:p} -> N",&N);     // Печатаем адрес.
    let x = &N;      // Берём адрес.
    println!("{} = N",N);
    println!("{} = N",*x);  // Печатаем значение по адресу, который хранится в x.
    println!("{:p} -> N",x);    // Печатаем адрес.
	{
    	println!("{:p} -> N",&N);
    	let x = &N;
    	println!("{} = N",N);
    	println!("{} = N",*x);
    	println!("{:p} -> N",x);
	}
    other();
}

fn other() {
    println!("{:p} -> N",&N);
    let x = &N;
    println!("{} = N",N);
    println!("{} = N",*x);
    println!("{:p} -> N",x);
}


Выход:

0x800fa114 -> N
3 = N
3 = N
0x800fa114 -> N
0x800fa114 -> N
3 = N
3 = N
0x800fa114 -> N
0x800fa114 -> N
3 = N
3 = N
0x800fa114 -> N


Как видим мы можем брать адрес константы и с его помощью её использовать, но, думаю, лучше так не делать, т.к.
«ссылки на одну и ту же постоянную не обязаны указывать на один и тот же адрес в памяти»


Ну, про const всё. Потом про static чего-нибудь расскажу…

Литература:

The Rust Reference (английский)
The Rust Programming Language (английский)
The Rust Programming Language (русский)

Комментарии (13)


  1. ozkriff
    24.01.2016 11:10

    > Как видим мы можем брать адрес константы и с его помощью её использовать, но думаю лучше так не делать, т.к. «ссылки на одну и ту же постоянную не обязаны указывать на один и тот же адрес в памяти»

    Многие функции одалживают значения, почему бы не передавать туда константы? Пускай себе в ссылки в разные места на стеке будут, я тут плохого не вижу.


  1. Lite4
    24.01.2016 11:12

    Если большая часть интуитивно понятна, то мне, как пришедшему из мира интерпретируемых языков, вот это немного неожиданно:

    fn main() {
        println!("N = {}",N);
        const N: i32 = 8;
    }
    

    Интересно, в c/c++ есть похожее поведение?


    1. Aquahawk
      24.01.2016 12:01

      Аналог этого есть даже в js: habrahabr.ru/post/127482
      не совсем так, но очень похоже.


      1. ozkriff
        24.01.2016 12:52

        Это другое же, в ржавчине область видимости нормально ограничена блоками, просто не требуется предварительное объявление, потому что компиляторы уже н-цать десятилетий как не однопроходные.


        1. Aquahawk
          24.01.2016 13:51

          Вы бы подробнее статью прочитали. Дело не в блоках. Дело в том что а js все переменные реально объявляются при входе в область видимости, а значения им присваиваются уже по ходу. и использование переменной выше объявления работает не совсем ожидаемо для многих людей.


          1. kstep
            24.01.2016 14:36
            +1

            Вы имеете в виду так называемый variable hoisting. Что ж, чем то поведение JS похоже в этом плане Rust, но механизмы в Rust совершенно другие действуют. В общем виде у Rust нету hoisting, как это есть в JS,


            1. kstep
              24.01.2016 14:40
              +2

              В JS объявление переменных и их инициализирование разделены, то есть hoisting выносит наверх функции объявление переменной, но инициализация её значением происходит там, где стоит var. С константами в Rust немного по другому: они объявляются на уровне блока (а не функции) и время инициализации строго привязано ко времени объявления. Объявленных и неинициализированных констант (как и статиков) быть не может. Просто объявление-инициализация констант происходит на один проход раньше, чем подстановка значений констант в места использования.


              1. kstep
                24.01.2016 14:47
                +1

                Если же брать объявление простых перменных, то никакого поведения, похожего на hoisting с ними нет и в помине. Да, переменные (объявленные через let) могут быть объявлены раньше, чем проинициализированы, но не могут использоваться до инициализации, и не могут быть использованы до объявления. То есть вот такой фокус:

                fn main() {
                    println!("{}", a);
                    let a = 123;
                }
                

                не пройдёт. Будет ошибка компиляции.


                1. monolithed
                  24.01.2016 16:00

                  В кстати JS тоже есть let

                  {
                     console.log(x); // ReferenceError
                     let x = 1;
                  }
                  


    1. ozkriff
      24.01.2016 14:44

      В плюсах такого нет, там для всего требуется предварительное объявление.


  1. Aquahawk
    24.01.2016 12:08
    +2

    const N: i32 = 4;
    fn main() {
        println!("N = {}",N);
        const N: i32 = 8;
        println!("N = {}\n",N);
        other();
    }
    
    fn other() {
        println!("N = {}",N);
    }
    
    

    Это мне конечно не нравится. Т.е. я понимаю почему это происходит, но мне это кажется категоричестки странным для языка такого позиционирования как RUST. Для js подобное раздолбайство допустимо, но строгий язык мог быть строже в этом месте.


    1. KvanTTT
      24.01.2016 14:26
      +2

      Лучше так вообще не делать)


    1. ozkriff
      25.01.2016 08:41

      Можно вот это, например, из clippy к своему проекту прикрутить, если кажется опасным:

      https://github.com/Manishearth/rust-clippy/wiki#shadow_unrelated