Я читаю Осваиваем ржавчину. В конце первой главы есть упражнение, в котором приводится пример кода, и задача состоит в том, чтобы исправить его, итерируя, используя в целом весьма полезные сообщения об ошибках компилятора.
Я был ожидание, что следующее было ошибкой, но это не является:
for line in reader.lines() {
let line = line.expect("Could not read line.");
Для полного контекста у меня есть весь код вкратце. Это код после того, как я все исправил, и соответствующие строки 37 и 38. Однако он требует подачи текстового файла в качестве аргумента.
Я ожидал ошибки, потому что line
находится в стеке (по крайней мере, указатель). Правильно ли, что он все еще может быть уничтожено и заменено без жалоб?
Что происходит под капотом в отношении управления памятью и стеком? Я предполагать, что line
на самом деле является ссылкой на строку (тип &str
). Итак, это нормально, потому что в любом случае сам указатель — объект в стеке — это просто usize
, так что оба line
объекта имеют одинаковый размер в стеке.
Могу ли я сделать это с чем-то другого размера? Могла ли вторая строка сказать:
let line: f64 = 3.42;
В этом случае сам объект находится в стеке, и он потенциально больше, чем usize
.
Всякий раз, когда переменная объявляется с помощью let
, это совершенно новая переменная, отдельная от всего, что было до нее. Даже если переменная с таким именем уже существует, исходная переменная — затененный, пока новая переменная находится в области действия. Если переменная затенена, она обычно недоступна.
Можно получить доступ к значению старой переменной в ситуациях, когда старая переменная все еще находится в области видимости после того, как новая переменная выходит из области видимости, или если старая переменная имеет реализацию Drop
.
Мы можем увидеть это в действии в следующем примере.
#[derive(Debug)]
struct DroppedU32(u32);
impl Drop for DroppedU32 {
fn drop(&mut self) {
eprintln!("Dropping u32: {}", self.0);
}
}
fn main() {
let x = 5;
dbg!(&x); // the original value
{
let x = 7;
dbg!(&x); // the new value
}
dbg!(&x); // the original value again
let y = DroppedU32(5);
dbg!(&y); // the original value
let y = DroppedU32(7);
dbg!(&y); // the new value
// down here, when the variables are dropped in
// reverse order of declaration,
// the original value is accessed again in the `Drop` impl.
}
Это не означает, что исходная переменная гарантированно все еще существует. Оптимизация компилятора может привести к перезаписи исходной переменной, особенно если к исходной переменной больше не обращаются.
Код
pub fn add_three(x: u32, y: u32, z: u32) -> u32 {
let x = x + y;
let x = x + z;
x
}
example::add_three:
lea eax, [rdi + rsi]
add eax, edx
ret
Если вы похожи на меня и не слишком знакомы с кодом на ассемблере, это в основном
Итак (помимо входных параметров), используется только одна переменная, хотя мы дважды использовали let x = ...
. Промежуточный результат let x = x + y;
перезаписывается.
Спасибо, @SCappella! Это отвечает на наиболее моего замешательства, но теперь у меня другое. Я понимаю, что происходит логически, но не понимаю, что происходит в распределении памяти и абстракциях Rust с нулевой стоимостью. В случае, когда я тень, но в том же масштабе, как и в вашем первом примере (let x=5;
... let x=7
), поскольку это та же область видимости, области, где x=5
, больше нет. Итак, что в этом случае происходит с let x:i32 = 5
... let x:u64 = 7
? Мой вопрос: у меня был 32-битный элемент в стеке, который теперь является 64-битным элементом в той же позиции стека, верно? Как я могу это сделать?
@MikeWilliamson Они не занимают одну и ту же позицию в стеке. Исходная переменная становится недоступной, но она по-прежнему существует в памяти без изменений. Я добавлю пример, чтобы показать это.
@MikeWilliamson Лучший способ подумать об этом - рассматривать внутреннюю x
как совершенно другую переменную с другим именем. Представьте, что компилятор даже переименование внутренне (или ведет себя как как будто). Тогда очевидно, что они не занимают одну и ту же позицию в стеке, и нет проблем с тем, что они имеют разные типы.
Отлично, спасибо @SCappella! Кстати, как вы можете получить байт-код или во что он компилируется? В Python я использовал библиотеку dis
. Есть ли что-то подобное в Rust? И если да, то как это управляется, поскольку Rust компилируется под определенные архитектуры/операционные системы. (Будет ли одна и та же программа создавать разные байт-коды на разных платформах? Или байт-код является просто посредником, как в JVM или Python?)
@MikeWilliamson Если вы хотите получить сборку с помощью Cargo, см. этот вопрос. Если вы согласны с онлайн-инструментами, ознакомьтесь с Проводник компилятора. Игровая площадка ржавчины также имеет параметры для создания сборки и других промежуточных представлений, которые использует Rust. Что касается других вопросов, попробуйте задать новый вопрос, и я уверен, что вы получите хороший ответ.
@Stargateur Я не думаю, что это дубликат, потому что мой основной вопрос не Зачем, это нормально, а как, это нормально? Как все происходит в стеке под капотом, чтобы это стало возможным? Есть просто 2 разных объекта, один старый
line
, а другой новыйline
, хотя старый больше недоступен? Или старый действительно перезаписывается (если в том же объеме)? Если да, то как это возможно для типов разного размера?