2015-07-31 3 views
14

Из Rust book о том, как мутировать структуры полей:Понимание структура поля мутации

let mut point = Point { x: 0, y: 0 }; 
point.x = 5; 

и позже:

Изменчивость является свойством связывания, а не самой структуры.

Это кажется нелогичным для меня, потому что point.x = 5 не выглядит, как я подмена переменной point. Есть ли способ объяснить это, чтобы он был более интуитивным?

Единственный способ, которым я могу обернуть мою голову вокруг этого, чтобы «представить себе», что я подменой point к копии оригинального Point с другим x значением (даже не уверен, что это точно).

ответ

5

У меня было такое же замешательство. Для меня это произошло из двух разных недоразумений. Во-первых, я пришел с языка, где переменные (aka bindings) были неявно ссылками на значения. На этом языке было важно провести различие между мутацией ссылки и изменением значения, которое было упомянуто. Во-вторых, я подумал, что «сама структура» в книге относится к инстанцируемому значению, но «структурой» это означает спецификацию/декларацию, а не конкретное значение этого типа.

Переменные в ржавчине различны.От reference:

переменная является составной частью кадра стека ...

Локальная переменная (или стек локального распределения) имеет значение непосредственно, выделенную в пределах памяти стека. Значение является частью стека кадра.

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

Последствием является то, что переупорядочивание переменной в смысле ее изменения для обозначения другого куска памяти несовместимо с моделью памяти Руста. (n.b. let x = 1; let x = 2; создает две переменные.)

Таким образом, в книге указывается, что изменчивость объявляется на уровне «на кусок памяти», а не как часть определения структуры.

Единственный способ, которым я могу обернуть мою голову вокруг этого, чтобы «представить себе», что я перекомпоновка пункт копию исходной точки с различными значениями х (даже не уверен, что это точно)

Вместо этого представьте, что вы меняете одну из 0 в куске памяти на 5; и что это значение находится в памяти, обозначенной point. Интерпретировать «привязка изменчива» означает, что вы можете мутировать кучу памяти, обозначенную привязкой, включая мутацию только ее части, например. установив поле struct. Подумайте о том, чтобы перепроверять переменные ржавчины так, как вы описываете, как не выраженные в Rust.

+0

Вы задали мне правильный путь Мне нужно было написать демоверсию, чтобы увидеть ее в действии (см. Мой ответ). – Kelvin

+0

@ Kelvin Спасибо за публикацию вашей демонстрации. Это хороший способ поставить ее. –

6

Здесь «привязка» не является глаголом, это существительное. Вы можете сказать, что привязки Rust являются синонимами переменных. Таким образом, вы можете прочитать этот отрывок, как

Mutability является свойством переменной, а не самой структуры.

Теперь, я думаю, это должно быть ясно - вы помечаете переменную как изменяемую, и вы можете изменить ее содержимое.

+2

Хорошо, я думаю, что это помогает. Таким образом, как будто 'mut' перед переменной просто применяет переменную * свойство * к переменной. И это свойство подразумевает определенные вещи, например. переупорядочивание и модификация структурных полей. Это точно? – Kelvin

+0

Да, вы в основном правы (кроме того, что 'mut' не влияет на повторное связывание в том смысле, что вы все равно можете написать' let x = 10, пусть x = 12', даже если 'x' не' mut'. mut' фактически разрешает присваивание в значение (либо сама переменная, либо поле внутри нее, если она является структурой), и снова ссылаясь на значения или подполе. –

10

Это кажется контр-интуитивным для меня, потому что point.x = 5 не похоже, что я переплетаю переменную точку. Есть ли способ объяснить это, чтобы он был более интуитивным?

Все это говорит, что ли или не то, что изменчиво определяется let - заявление (связывание) переменной, в отличие от того, чтобы быть собственностью типа или какой-либо конкретной области.

В примере, point и его поля изменчиво, потому что point вводится в let mut заявление (в отличие от простого let заявления), а не из-за какого-то имущества Point типа в целом.

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

type point = 
    { x: int; 
    mutable y: int; 
    }; 

Это означает, что вы можете мутировать y поле каждого значения point, но вы никогда не сможете мутировать x.

4

@ Ответ m-n поставил меня на правильный трек. Это все о адресах стека! Вот демонстрация, которая укрепила в моем сознании то, что на самом деле происходит.

struct Point { 
    x: i64, 
    y: i64, 
} 

fn main() { 
    { 
     println!("== clobber binding"); 
     let a = 1; 
     println!("val={} | addr={:p}", a, &a); 
     // This is completely new variable, with a different stack address 
     let a = 2; 
     println!("val={} | addr={:p}", a, &a); 
    } 
    { 
     println!("== reassign"); 
     let mut b = 1; 
     println!("val={} | addr={:p}", b, &b); 
     // uses same stack address 
     b = 2; 
     println!("val={} | addr={:p}", b, &b); 
    } 
    { 
     println!("== Struct: clobber binding"); 
     let p1 = Point{ x: 1, y: 2 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     let p1 = Point{ x: 3, y: 4 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 
    } 
    { 
     println!("== Struct: reassign"); 
     let mut p1 = Point{ x: 1, y: 2 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     // each of these use the same addresses; no new addresses 
     println!(" (entire struct)"); 
     p1 = Point{ x: 3, y: 4 }; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 

     println!(" (individual members)"); 
     p1.x = 5; p1.y = 6; 
     println!(
      "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}", 
      p1.x, p1.y,   &p1,   &p1.x,  &p1.y); 
    } 
} 

Output (адреса, очевидно, немного отличается в перспективе):

== clobber binding 
val=1 | addr=0x7fff6112863c 
val=2 | addr=0x7fff6112858c 
== reassign 
val=1 | addr=0x7fff6112847c 
val=2 | addr=0x7fff6112847c 
== Struct: clobber binding 
xval,yval=(1, 2) | pointaddr=0x7fff611282b8, xaddr=0x7fff611282b8, yaddr=0x7fff611282c0 
xval,yval=(3, 4) | pointaddr=0x7fff61128178, xaddr=0x7fff61128178, yaddr=0x7fff61128180 
== Struct: reassign 
xval,yval=(1, 2) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 
    (entire struct) 
xval,yval=(3, 4) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 
    (individual members) 
xval,yval=(5, 6) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0 

Ключевые моменты таковы:

  • Использование let для "затирать" существующую привязку (новый адрес стека). Это происходит, даже если переменная была объявлена ​​mut, поэтому будьте осторожны.
  • Используйте mut для повторного использования существующего адреса стека, но не используйте let при переназначении.

Этот тест показывает несколько интересных вещей:

  • Если вы переназначить все изменяемые-структуру, это эквивалентно отнесение каждого члена в отдельности.
  • Адрес переменной, содержащей структуру, совпадает с адресом первого элемента. Я предполагаю, что это имеет смысл, если вы исходите из фона C/C++.