🐡

Mojo の __moveinit__ を適用させるには __del__ が必要

2023/09/09に公開
2

はじめに

Pythonと互換性がありながら、高速化された新言語 Mojoが一般公開され、色々触ってみています。

Mojo の特徴として、型に以下の3種類が存在することが挙げられます。

  • ムーブ・コピー不可
  • ムーブ可能
    • __moveinit__ の実装が条件
  • コピー・ムーブ可能
    • __copyinit__ の実装が条件
    • ムーブの時には__moveinit__ があればそちらがよばれるし、なければ__copyinit__が呼ばれる

この3種類をうまく使うことで所有権の管理が可能です。
また

a = b^

のような ^ transfer 演算子が呼ばれた際には、__moveinit__ が呼ばれます。

実験してみる

早速どのようの __copyinit____moveinit__ が呼ばれるのか実験してみましょう!

struct Pair:
    var x: Int
    var y: Int

    fn __init__(inout self, x: Int, y: Int):
        print("Running init")
        self.x = x
        self.y = y
    
    fn __moveinit__(inout self, owned existing: Self):
        print("move init")
        self.x = existing.x
        self.y = existing.x

    fn __copyinit__(inout self, existing: Self):
        print("copy init")
        self.x = existing.x
        self.y = existing.y

fn main():
    let p = Pair(1, 2)
    let copied = p.copy()
    print("copy ended\n")
    let moved = (p^).copy()
    let moved2 = (moved^).copy()
    let moved3 = copy(moved2^)
    let moved4 = moved3^

結果がこちら

init
copy init
copy init
copy ended

copy init
copy init
copy init
move init

最後の

let moved4 = moved3^

以外で、全然__moveinit__が呼ばれてない!なんで!!

__del__ をつけてみる

そういえばPairには__del__が実装されていませんでした。実装してから試してみましょう!

struct Pair:
    var x: Int
    var y: Int

    fn __init__(inout self, x: Int, y: Int):
        print("init")
        self.x = x
        self.y = y
    
    fn __moveinit__(inout self, owned existing: Self):
        print("move init")
        self.x = existing.x
        self.y = existing.x

    fn __copyinit__(inout self, existing: Self):
        print("copy init")
        self.x = existing.x
        self.y = existing.y
    
    fn __del__(owned self): # added here.
        print("deleted")
    
    fn copy(owned self) -> Self:
        return self

fn copy(owned pair: Pair) -> Pair:
    return pair

fn main():
    let p = Pair(1, 2)
    let copied = p.copy()
    print("copy ended\n")
    let moved = (p^).copy()
    let moved2 = (moved^).copy()
    let moved3 = copy(moved2^)
    let moved4 = moved3^

結果がこちら

init
copy init
move init
deleted
copy ended

move init
move init
move init
move init
deleted

ちゃんと__moveinit__が呼ばれてる!!
let copied = p.copy()の際もcopy2回からcopy -> move になってますね(後半のmovecopy()されたのが代入によって右辺値として^なしにmoveされたからだと思われます)。

というわけで、忘れずに、__del__ を実装しましょうという話でした。

Discussion

TaqqnTaqqn

追記

毎回 __init__ __moveinit__, __copyinit__ を実装するのは大変なので、@value デコレータが存在する。Kotlin の data class 的なやつ。

@value
struct Pair:
    var x: Int
    var y: Int

fn main():
  let pair = Pair(5, 10)

  # Move object
  var pair2 = pair^
  # Copy object
  let pair3 = pair2
  # Edit original
  pair2.x = 20

  # Print both the original and copy
  print(pair2.x)
  print(pair3.x)

https://mojodojo.dev/guides/decorators/value.html