Mojo の borrowed, inout, owned
Pythonと互換性がありながら、高速化された新言語 Mojoが一般公開されたので、色々コンパイラで遊んでみました。
その研究記録をここに残します!!
borrowed, inout, owned について
Mojo には関数の引数として borrowed, inout, owned を取ることができます。それぞれ引数がどのように関数内で扱われるかが異なります。
RustやC++の参照・ポインタを思い出します。
borrowed
まず基本的なのが、borrowed
です。これは Mojo の引数のデフォルトの設定であり、省略が可能です。
つまり
fn add(borrowed x: Int, borrowed y: Int) -> Int:
return x + y
と書こうと、
fn add(x: Int, y: Int) -> Int: # 型アノテーション
return x + y
と書こうと等価です。以降では、borrowed
は書かない書き方で説明します。
borrowed な変数は不変参照 (immutable references)です。したがって次のような変数の変更は行えません。Rust でいう &T
に近いです。
fn add_y_to_x(x: Int, y: Int):
x += y # error: expression must be mutable for in-place operator destination
inout
borrowed
が不変参照だったのに対し、inoutは可変参照です。上のadd_y_to_x
はx
をinout
にすることでエラーが消えます。Rustでいう&mut T
に近いです。
fn add_y_to_x(inout x: Int, y: Int):
x += y
fn main():
var x = 1
let y = 2
add_x_to_y(x, y)
print(x)
inout
な引数にはvar
または Python的な変数しか渡せません。
fn main():
let x = 1 # ここが var でないといけない
let y = 2
add_x_to_y(x, y) # error: invalid call to 'add_x_to_y': argument #0 must be mutable in order to pass as a by-ref argument
print(x)
Pythonとの相互運用がどこまで可能なのかというと、以下は動作します。
fn add_y_to_x(inout x: Int, y: Int):
x += y
def main():
x = 1 # let でも var でもない、非効率な変数。
let y = 2
add_y_to_x(x, y)
print(x)
owned
owned は(後ほど説明するtransfer 演算子を使わない場合は)引数に渡された場合、コピーを作成します。RustでいうT
に近いです。
fn set_fire(owned text: String) -> String:
text += "🔥"
return text
fn main():
let a: String = "mojo"
let b = set_fire(a) # a のコピーが作成され、bに代入される。aには"🔥"が結合されない。
print(a)
print(b)
コピーがいらないという場合はtransfer演算子を使えば、aという変数の寿命をそこで終わらせることで、コピーなしでset_fire
関数を呼べます。また、a^
を呼び出した後にa
を使えないことをコンパイラが処理してくれます。
fn main():
let a: String = "mojo"
let b = set_fire(a^) # a から set_fire 内の変数にtransfer
print(a) # error: use of uninitialized value 'a'
print(b)
ちなみに、a^
した後は、let a = ""
と再宣言することもできません。
fn main():
let a: String = "mojo"
let b = set_fire(a^)
let a = "" # error: invalid redefinition of 'a'
print(b)
ここでクイズ!
次はどんな挙動になるでしょう。
fn main():
let a = 0
var b = 1
b = a
b += 1
print(a)
正解
0
が出力されます。
b = a
の時には必ずコピーが発生するようです。
__copyinit__
が実装されていない場合には、cannot be copied into its destination
というエラーが出ます。
struct における inout など
struct
でも self
の inout
指定をすることがあります。これはただ単純に構造体のインスタンスがself
に代入されたと考えればいいです。Rustでも&self
&mut self
self
があったのと同様です。
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = 1
self.second = second
fn dump(self):
print(self.first, self.second)
fn add_second_to_first(inout self):
self.first += self.second
var p = MyPair(1, 2) # __init__の時にはselfに何らか変更を加える。
p.dump() # dump(a) のイメージ
p.add_second_to_first() # add_second_to_first(p) のイメージ
p.dump() # 3, 2
なお、__init__
では必ず、inout
なself
を渡す必要があります。
struct MyPair:
fn __init__(self): # error: 'self' in struct '__init__' must be passed as mutable reference
pass
struct は __copyinit__
を実装していないとコピーできない (a = b
のような代入もできない)ので、owned
として引数に呼ばれることもできません。
__copyinit__
が実装された状態では、以下のように動作します。
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn dump(self):
print(self.first, self.second)
fn add_second_to_first(inout self):
self.first += self.second
fn copy_and_update_first(owned self, first: Int) -> Self:
self.first = first
return self
fn __copyinit__(inout self, rhs: Self): # これがいないとそもそもcopyができない
self.first = rhs.first
self.second = rhs.second
fn main():
var p = MyPair(1, 2)
p.dump() # 1, 2
let copied = p.copy_and_update_first(-100)
p.dump() # 1, 2
copied.dump() # -100, 2
let moved = (p^).copy_and_update_first(100) # __moveinit__ の実装は不要
moved.dump() # 100, 2
より詳細な __copyinit__
__moveinit__
周りの話は以下の記事を参照してください。
structでも同様のクイズ!
fn main():
let a = MyPair(1, 2)
var b = MyPair(3, 4)
b = a
b.add_second_to_first()
a.dump()
これはどうなるでしょう?そもそもコンパイルは通るでしょうか?
正解
コンパイルが通り、
1, 2
が出力されます。つまり不変な a
は変更されません。
代入操作ではコピーが作成されます。
今までの知識でMyIntを実装
__copyinit__
というコピーの時に利用されるコンストラクタと、__add__
__iadd__
のようなPythonにあった演算子オーバーロードを使うことでこのように、Intのラッパーを作ることが可能です。
struct MyInt:
var value: Int
fn __init__(inout self, v: Int):
self.value = v
fn __copyinit__(inout self, other: MyInt):
self.value = other.value
fn __add__(self, rhs: MyInt) -> MyInt:
return MyInt(self.value + rhs.value)
fn __iadd__(inout self, rhs: MyInt):
self = self + rhs
Discussion
その他をまとめた記事