Rustで高階関数を使う
はじめに
高階関数は便利だ。
なので、rustでも使いたい。
よって、使い方を検証した。
下記にrust環境構築方法をまとめている。
dockerとvscodeでrust環境を構築する on Windows10 - イヌツムリのメモ
結論
引数になる関数をBox
でラップすると、関数を引数に取れる。
戻り値になる関数をimpl
を指定すると関数を戻り値に取れる。
dyn
のありなしで何が変わるのか調べる。 -> traitオブジェクトを引数に取るときはdynをつける。
(2021/04/07 追記)
関数を引数にとるサンプルコード
fn map(f: Box<dyn Fn(i32) -> i32>, a: &Vec<i32>) -> Vec<i32>{ let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn twice(x: i32) -> i32{ x + x } fn main() { let arry = &vec![4, 5, 3, 2]; let v4 = map(Box::new(twice),arry); println!("{:?}", v4); }
引数に渡すときはBox::new(関数名)
で渡せる。
受け取り側の関数定義はBox<dyn Fn(i32) -> i32>
で受け取れる。
(2021/04/07 追記)
dynをつけないと下記エラーでコンパイラに怒られる。
trait objects without an explicit
dynare deprecated
トレイトを引数にとるのでなく、トレイトオブジェクトを引数に取るので、ということらしい。
トレイトではどんな関数が実装されているか不明で、メモリ確保ができないから、と理解した。
オブジェクト化されていれば、内容が明確なのでメモリ確保が可能になる。
関数を返すサンプルコード
fn map(f: Box<dyn Fn(i32) -> i32>, a: &Vec<i32>) -> Vec<i32>{ let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn add_x(x: i32) -> impl Fn(i32) -> i32{ move |a: i32| -> i32{ a + x } } fn main() { let arry2 = &vec![4, 5, 3, 2]; let f = add_x(4); let v2 = map(Box::new(f),arry2); println!("{:?}", v2); }
関数を戻り値に指定するにはimpl
を指定する。
関数を変数に束縛する際に引数を与える場合、move
しないと与えた変数の所有権を移せない。
Boxよりもimplを使ったほうがよい?
(2021/04/07 追記)
dynはトレイトオブジェクトを返すときに明示するとわかった。
なので、add_xの戻り値は、implでなく下記のようにBox
fn add_x(x: i32) -> Box<dyn Fn(i32) -> i32>{ let f = move |a: i32| -> i32{ a + x }; Box::new(f) } fn main(){ let arry2 = &vec![4, 5, 3, 2]; let f = add_x(5); let v2 = map(f,arry2); println!("{:?}", v2); }
というか、下記のようにimpl使ったほうがスッキリとかける。
(i32をTに変えてるのは気にしないで...)
map()の宣言時や、mapの引数にtwiceを与えるときにBoxを使わなくて良いのでスッキリする。
ただ、dynがあとからrustに実装されたことを考えると、私の理解に間違いがあるように思う。
fn map<T>(f: impl Fn(T) -> T, a: &Vec<T>) -> Vec<T> where T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T> { let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn twice(x: i32) -> i32{ x + x } fn main(){ let arry1 = &vec![4, 5, 3, 2]; let v4 = map(twice, &arry1); println!("{:?}", v4); }