Cover Image for [Rust] 高階関数用のfunctionの型定義を試す

[Rust] 高階関数用のfunctionの型定義を試す

概要

高階関数を作るときに、functionの型をどう定義するべきか。Rustだと色々なやり方がありそうなので試してみる。

結論

  • ただ単に function の型が欲しい時は type と fn で定義する
  • このfunctionを持った struct を引数に取りたい場合は trait を使って trait 境界を設定した方がいい
    • self を引数に取らないメソッドは trait 境界を用いて利用することはできないので注意

type で function の型定義をする

type AddNumber = fn(usize, usize) -> usize;
type Square = fn(usize) -> usize;

pub fn add_then_square(add: AddNumber, square: Square, left: usize, right: usize) -> usize {
    square(add(left, right))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let add: AddNumber = |x, y| x + y;
        let square: Square = |x| x * x;
        let result = add_then_square(add, square, 2, 2);
        assert_eq!(result, 16);
    }
}

(2023-06-04追記) 注意としては以下のようなジェネリクスを用いた関数ポインタの型エイリアスはサポートしていない

// これはrustでは動かない
type GenericFn<T> = fn(T) -> T where T: SomeTrait;

trait を使う

pub trait Adder {
    fn add(&self, left: usize, right: usize) -> usize;
}

pub trait Squarer {
    fn square(&self, x: usize) -> usize;
}

struct Math {}

impl Adder for Math {
    fn add(&self, left: usize, right: usize) -> usize {
        left + right
    }
}

impl Squarer for Math {
    fn square(&self, x: usize) -> usize {
        x * x
    }
}

pub fn add_then_square(
    adder: &impl Adder,
    squarer: &impl Squarer,
    left: usize,
    right: usize,
) -> usize {
    squarer.square(adder.add(left, right))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let math = Math {};
        let result = add_then_square(&math, &math, 2, 2);
        assert_eq!(result, 16);
    }
}

注意しないといけないことが一つあって、trait に self を引数に取らないメソッドを定義する場合、そのメソッドを struct に実装することはできるが trait境界 を定義して受け取った側のメソッドではそのメソッドを利用できない。

以下はエラーになるパターン

pub trait Adder {
    fn add(left: usize, right: usize) -> usize;
}

pub trait Squarer {
    fn square(x: usize) -> usize;
}

struct Math {}

impl Adder for Math {
    fn add(left: usize, right: usize) -> usize {
        left + right
    }
}

impl Squarer for Math {
    fn square(x: usize) -> usize {
        x * x
    }
}

pub fn add_then_square(
    adder: &impl Adder,
    squarer: &impl Squarer,
    left: usize,
    right: usize,
) -> usize {
	// square と add が method name not found になる
    squarer.square(adder.add(left, right))
}

(2023-06-04追記) こうやって実体型の方を参照すれば呼ぶことができるが、高階関数にはならない

pub trait Adder {
    fn add(left: usize, right: usize) -> usize;
}

pub trait Squarer {
    fn square(x: usize) -> usize;
}

struct Math {}

impl Adder for Math {
    fn add(left: usize, right: usize) -> usize {
        left + right
    }
}

impl Squarer for Math {
    fn square(x: usize) -> usize {
        x * x
    }
}

pub fn add_then_square(
    left: usize,
    right: usize,
) -> usize {
    Math::square(Math::add(left, right)) // 実体型の方を呼ぶ
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add_then_square(2, 2);
        assert_eq!(result, 16);
    }
}