Ключевое слово 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)
Lite4
24.01.2016 11:12Если большая часть интуитивно понятна, то мне, как пришедшему из мира интерпретируемых языков, вот это немного неожиданно:
fn main() { println!("N = {}",N); const N: i32 = 8; }
Интересно, в c/c++ есть похожее поведение?Aquahawk
24.01.2016 12:01Аналог этого есть даже в js: habrahabr.ru/post/127482
не совсем так, но очень похоже.ozkriff
24.01.2016 12:52Это другое же, в ржавчине область видимости нормально ограничена блоками, просто не требуется предварительное объявление, потому что компиляторы уже н-цать десятилетий как не однопроходные.
Aquahawk
24.01.2016 13:51Вы бы подробнее статью прочитали. Дело не в блоках. Дело в том что а js все переменные реально объявляются при входе в область видимости, а значения им присваиваются уже по ходу. и использование переменной выше объявления работает не совсем ожидаемо для многих людей.
kstep
24.01.2016 14:36+1Вы имеете в виду так называемый variable hoisting. Что ж, чем то поведение JS похоже в этом плане Rust, но механизмы в Rust совершенно другие действуют. В общем виде у Rust нету hoisting, как это есть в JS,
kstep
24.01.2016 14:40+2В JS объявление переменных и их инициализирование разделены, то есть hoisting выносит наверх функции объявление переменной, но инициализация её значением происходит там, где стоит var. С константами в Rust немного по другому: они объявляются на уровне блока (а не функции) и время инициализации строго привязано ко времени объявления. Объявленных и неинициализированных констант (как и статиков) быть не может. Просто объявление-инициализация констант происходит на один проход раньше, чем подстановка значений констант в места использования.
kstep
24.01.2016 14:47+1Если же брать объявление простых перменных, то никакого поведения, похожего на hoisting с ними нет и в помине. Да, переменные (объявленные через let) могут быть объявлены раньше, чем проинициализированы, но не могут использоваться до инициализации, и не могут быть использованы до объявления. То есть вот такой фокус:
fn main() { println!("{}", a); let a = 123; }
не пройдёт. Будет ошибка компиляции.monolithed
24.01.2016 16:00В кстати JS тоже есть let
{ console.log(x); // ReferenceError let x = 1; }
Aquahawk
24.01.2016 12:08+2const N: i32 = 4; fn main() { println!("N = {}",N); const N: i32 = 8; println!("N = {}\n",N); other(); } fn other() { println!("N = {}",N); }
Это мне конечно не нравится. Т.е. я понимаю почему это происходит, но мне это кажется категоричестки странным для языка такого позиционирования как RUST. Для js подобное раздолбайство допустимо, но строгий язык мог быть строже в этом месте.ozkriff
25.01.2016 08:41Можно вот это, например, из clippy к своему проекту прикрутить, если кажется опасным:
https://github.com/Manishearth/rust-clippy/wiki#shadow_unrelated
ozkriff
> Как видим мы можем брать адрес константы и с его помощью её использовать, но думаю лучше так не делать, т.к. «ссылки на одну и ту же постоянную не обязаны указывать на один и тот же адрес в памяти»
Многие функции одалживают значения, почему бы не передавать туда константы? Пускай себе в ссылки в разные места на стеке будут, я тут плохого не вижу.