別名参照問題と値オブジェクト
勉強になった。「別名参照問題」というワードを正直初めて聞いた(そういうコードが書けてしまうことは認識していたけれど、名前がついているとは思わなかった)。特定のプログラミング言語では値オブジェクトをイミュータブルにする必要がある理由はこれ。
ところで Rust では別名参照問題は発生しないとのことだが、本当にそうなのか確かめてみることにした。確かめるまでもなく起こらなさそうな気がしたけれど。
Rust のコードで Java っぽく書くとこんな感じになる。
#[derive(Debug)]
struct FullName {
family_name: String,
first_name: String,
}
impl FullName {
pub fn set_family_name(&mut self, family_name: impl Into<String>) {
self.family_name = family_name.into();
}
pub fn set_first_name(&mut self, first_name: impl Into<String>) {
self.first_name = first_name.into();
}
}
fn main() {
let mut fullname = FullName {
family_name: "Sasaki".to_string(),
first_name: "Kojiro".to_string(),
};
let another_fullname = fullname;
another_fullname.set_family_name("Sato");
println!("{:?}", fullname);
println!("{:?}", another_fullname);
}
ちなみに、上述のコードは Rust ではコンパイルエラーになる。
Compiling playground v0.0.1 (/playground)
warning: variable does not need to be mutable
--> src/main.rs:18:9
|
18 | let mut fullname = FullName {
| ----^^^^^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
error[E0596]: cannot borrow `another_fullname` as mutable, as it is not declared as mutable
--> src/main.rs:23:5
|
22 | let another_fullname = fullname;
| ---------------- help: consider changing this to be mutable: `mut another_fullname`
23 | another_fullname.set_family_name("Sato");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
error[E0382]: borrow of moved value: `fullname`
--> src/main.rs:24:22
|
18 | let mut fullname = FullName {
| ------------ move occurs because `fullname` has type `FullName`, which does not implement the `Copy` trait
...
22 | let another_fullname = fullname;
| -------- value moved here
23 | another_fullname.set_family_name("Sato");
24 | println!("{:?}", fullname);
| ^^^^^^^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0382, E0596.
For more information about an error, try `rustc --explain E0382`.
warning: `playground` (bin "playground") generated 1 warning
error: could not compile `playground` due to 2 previous errors; 1 warning emitted
というわけで、そもそも問題にならないのであった。めでたしめでたし。
したがって、kumagi さんの下記ツイートは「そもそも問題にならないのでうれしー!ということにはならない」というのが正かと思われる。
ちなみに参考までに Java のコードを置いておくと次のようになる。下記は別名参照問題が起きてしまっている。fullName
側は複製が行われているのであれば変更されていないべきだが、変更されてしまっている。
public class FullName {
private String familyName;
private String firstName;
public FullName(String familyName, String firstName) {
this.familyName = familyName;
this.firstName = firstName;
}
public String getFamilyName() {
return familyName;
}
public String getFirstName() {
return firstName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
class Playground {
public static void main(String[ ] args) {
FullName fullName = new FullName("Sasaki", "Kojiro");
FullName anotherFullName = fullName;
anotherFullName.setFamilyName("Sato");
System.out.println(fullName.getFamilyName() + " " + fullName.getFirstName());
System.out.println(anotherFullName.getFamilyName() + " " + anotherFullName.getFirstName());
}
}
出力結果は下記になっている。
Sato Kojiro
Sato Kojiro
あるいは、オブジェクトIDを出力するとより確認しやすい。
public class FullName {
private String familyName;
private String firstName;
public FullName(String familyName, String firstName) {
this.familyName = familyName;
this.firstName = firstName;
}
public String getFamilyName() {
return familyName;
}
public String getFirstName() {
return firstName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
class Playground {
public static void main(String[ ] args) {
FullName fullName = new FullName("Sasaki", "Kojiro");
FullName anotherFullName = fullName;
anotherFullName.setFamilyName("Sato");
System.out.println(fullName);
System.out.println(anotherFullName);
}
}
同じところを向いていそう。
FullName@d716361
FullName@d716361
ちなみにコピーセマンティクスが適用される側も見てみると、
fn main() {
let mut a = 1;
let b = a;
a = 2;
println!("a: {}, b: {}", a, b);
}
下記は別名参照問題が起きてはいない(コピーだからね)。
a: 2, b: 1
値オブジェクトそのものについては、そもそも Martin Fowler 側の定義もあるみたいな話を全然知らなかったので、そっちもきちんと読んでみたいと思った。DDD の値オブジェクトは、「ドメイン情報をいい感じに反映できる何か」という側面が強調されている傾向にあると、今なら相対的に認識できるようになった気がするけれど、別名参照問題を回避するといったそもそも値オブジェクトのそもそものアイディアを知らなかったのであった。