🔰

pythonをrustへ変換したときの対応をまとめる

2024/08/25に公開

もともとpythonのアプリがあって、それをrustへ書き直す作業をしていたことがありまして、
この記事ではその際につまづいた変換部分をまとめておきます。(随時追加予定)

それぞれの言語で特徴や目的が違うので、機能が対応しているわけではないです。
(例:pythonのdictをrustのstructに対応させるなど)
・python:処理が最後まで動けばOK。
・rust:テストが動けばよし。(またrustの経験が浅いので、技術的に難しいものは使っていません)

注意点
pythonの動的型付けとrustの静的型付けの違いは大きく、そのまま変換してもうまく動かないことがあります。
その場合、無理にrust側で柔軟な構造を定義するよりも、python側を修正する方が結果的に可読性の高いコードになることが多かったです。
(rust側で複雑な処理を作る必要が出てきたら、技術的負債がたまっているかも)

python dict 動的にkeyが決まる

config = {
    "key1": "value1",
    "key2": "value2",
}

print(config["key1"])
let config = HashMap::from([
    ("key1", "value1"),
    ("key2", "value2"),
]);

println!("{}", config["key1"]);

python dict 静的にkeyが決まる

dictのkeyが静的に決まる場合はrustのstructで定義してしまった方がvscodeの補完機能が効くので便利

config = {
    "key1": ["value1_1","value1_2","value1_3"],
    "key2": ["value2_1","value2_2","value2_3"],
}

print(config["key1"])

struct Config<'a> {
    key1: Vec<&'a str>,
    key2: Vec<&'a str>,
}

let config = Config {
    key1: vec!["value1_1", "value1_2", "value1_3"],
    key2: vec!["value2_1", "value2_2", "value2_3"],
};

println!("{:?}", config.key1);

python structured arrayの各行を繰り返し処理

pythonのpandasやnumpyでデータハンドリングをしているものについては、rustのstructによってrecordレベルの定義をしておく

import numpy as np

data = np.array([
    (1, 'Alice', 25),
    (2, 'Bob', 30),
    (3, 'Charlie', 35)
], dtype=[('id', 'i4'), ('name', 'U10'), ('age', 'i4')])

for row in data:
    print(row['id'],row['name'],row['age'])
pub struct DataFrame {
    pub rec: Vec<DtypesPerson>,
}

pub struct DtypesPerson {
    id: i32,
    name: String,
    age: i32,
}

let data = DataFrame {
    rec: vec![
        DtypesPerson { id: 1, name: String::from("Alice"), age: 25 },
        DtypesPerson { id: 2, name: String::from("Bob"), age: 30 },
        DtypesPerson { id: 3, name: String::from("Charlie"), age: 35 },
    ],
};

for row in &data.rec {
    println!("{} {} {}", row.id, row.name, row.age);
}

python if 単一の変数

x = 10

if x > 5:
    y = 20
else:
    y = 0

print(y)
let x = 10;

let y = if x > 5 {
    20
} else {
    0
};

println!("{}", y);

python if 複数の変数

x = 10

y = 0
z = 5

if x > 5:
    y = 20
    z = 30
else:
    y = 0

print(y,z)
let x = 10;

let mut y = 0; 
let mut z = 5; 

if x > 5 {
    y = 20;
    z = 30;
} else {
    y = 0; 
}

println!("{} {}", y, z);

python def 関数

import numpy as np

data = np.array([
    (1, 'Alice', 25, ''),
    (2, 'Bob', 30, ''),
    (3, 'Charlie', 35, '')
], dtype=[('id', 'i4'), ('name', 'U10'), ('age', 'i4'), ('greeting', 'U10')])


def add_greeting_column(data, suffixe):
    data["greeting"] = [i + suffixe for i in data["name"]]
    return data

suffixe = "!!!"
data_new = add_greeting_column(data, suffixe)

for row in data_new:
    print(row)


> (1, 'Alice', 25, 'Alice!!!')
> (2, 'Bob', 30, 'Bob!!!')
> (3, 'Charlie', 35, 'Charlie!!!')

↑のpythonコードではnumpyのdata_newを新たに作成しているけど、実は引数側のdataも書き変わっている
↓のrustだと所有権がdataからdata_newへ移動しているので、dataを再利用を防止できる


pub struct DataFrame {
    pub rec: Vec<DtypesPerson>,
}

pub struct DtypesPerson {
    id: i32,
    name: String,
    age: i32,
    greeting:String,
}

fn add_greeting_column(df: DataFrame,suffix: &String) -> DataFrame {
    let mut new_data = Vec::new();

    for row in &df.rec {
        new_data.push(DtypesPerson {
            id: row.id,
            name: row.name.clone(),
            age: row.age,
            greeting: row.name.clone() + &suffix,
        });
    }
    DataFrame { rec :new_data}
}

let data = DataFrame {
    rec: vec![
        DtypesPerson { id: 1, name: String::from("Alice"), age: 25, greeting: String::from("")},
        DtypesPerson { id: 2, name: String::from("Bob"), age: 30,  greeting: String::from("")},
        DtypesPerson { id: 3, name: String::from("Charlie"), age: 35,  greeting: String::from("")},
    ],
};

let suffix = "!!!".to_string();
let data_new = add_greeting_column(data, &suffix);


for row in &data_new.rec {
    println!("{:?} {:?} {:?} {:?}", row.id, row.name, row.age, row.greeting);
};

// 1 "Alice" 25 "Alice!!!"
// 2 "Bob" 30 "Bob!!!"
// 3 "Charlie" 35 "Charlie!!!"

主となる引数が1つあるようなケースだと、rustの所有権を移動する形で使うことが多い。(上の例だとdata:DataFrame)
そのほかちょっとした設定などの補助的なものは、それほど意識して区別していない(上の例だとsuffix)

GitHubで編集を提案

Discussion