Elias
Elias

Reputation: 4112

Function that accepts function that accepts trait implementation

Well the title says it all, but I'm just going to add my code as that makes it clearer what I want to do.

I have the following function:

fn calculate_rook_attacks_for(pos: &impl BoardPos, blockers: u64) -> u64 {
    unimplemented!()
}

I now want a higher order function that takes such calculation functions (not just this one, all functions that have this signature), but I'm struggling on the type definition. Mostly with the trait part.

I've tried:

fn generate_all_possible_attacks_for(
    calculate_moves_for: fn(impl BoardPos) -> u64
) {
    // cool generations stuff
}

But apparently that's not allowed:

impl Trait only allowed in function and inherent method return types, not in fn.

I've also tried:

fn generate_all_possible_attacks_for<T>(
    calculate_attacks_for: fn(&T, u64) -> u64,
)
where
    T: BoardPos
{
    // cool generation stuff
}

But that complains something about type inference when trying to call it:

cannot infer type for type parameter T declared on the function generate_all_possible_attacks_for

If I try to add the type: ::<&impl BoardPos>, I'm back the the previous error.

I've tried various other things (also stuff with &dyn), but I can't remember them all.

I'm sure the resources on how to do this are out there, I just lack the correct terminology to look it up.

(Btw, I don't want to use &dyn in the actual function call, as I've heard that results in a runtime performance impact)

Edit

Upon reading the answer I realized I'd have to specify the type at some point. I'm not sure what to do in the following case though:

fn generate_magic_number_for(pos: &impl BoardPos, piece: Piece) -> u64 {
    let idx = pos.idx();

    let (relevant_moves, number_of_relevant_moves, get_attacks_for): (
        u64,
        u64,
        fn(&impl BoardPos, u64) -> u64,
    ) = match piece {
        Piece::Bishop => (
            RELEVANT_BISHOP_MOVES_PER_SQUARE[idx],
            NUMBER_OF_RELEVANT_BISHOP_MOVES_PER_SQUARE[idx],
            piece::calculate_bishop_attacks_for,
        ),
        Piece::Rook => (
            RELEVANT_ROOK_MOVES_PER_SQUARE[idx],
            NUMBER_OF_RELEVANT_ROOK_MOVES_PER_SQUARE[idx],
            piece::calculate_rook_attacks_for,
        ),
        _ => panic!(
            "this function is only callable for bishops and rooks, was called with '{:?}'",
            piece
        ),
    };
    /// ...
}

Upvotes: 1

Views: 760

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170723

The type of calculate_rook_attacks_for expands to

fn calculate_rook_attacks_for<T: BoardPos>(pos: &T, blockers: u64) -> u64

So you'd need T in generate_all_possible_attacks_for's type to be in a different place, something like

fn generate_all_possible_attacks_for<F: for<T: BoardPos> Fn(&T, u64) -> u64>(
    calculate_attacks_for: F,
)

But this isn't legal (yet?). However, if your generate_all_possible_attacks_for only wants to call calculate_attacks_for with some specific implementation of BoardPos, you can just use it:

trait BoardPos {}

struct SomeBoardPos;
impl BoardPos for SomeBoardPos {}

fn calculate_rook_attacks_for(_pos: &impl BoardPos, _blockers: u64) -> u64 {
    0
}

fn generate_all_possible_attacks_for(
    calculate_attacks_for: impl Fn(&SomeBoardPos, u64) -> u64,
) -> u64 {
    calculate_attacks_for(&SomeBoardPos, 0)
}

fn main() {
    println!("{}", generate_all_possible_attacks_for(calculate_rook_attacks_for));
}

Playground

Unfortunately, if you want to call calculate_rook_attacks_for more than once with different implementations, you'll need to pass it the same number of times (so far as I know):

trait BoardPos {}

struct SomeBoardPos;
impl BoardPos for SomeBoardPos {}

struct SomeBoardPos2;
impl BoardPos for SomeBoardPos2 {}

fn calculate_rook_attacks_for(_pos: &impl BoardPos, _blockers: u64) -> u64 {
    0
}

fn generate_all_possible_attacks_for(
    calculate_attacks_for: impl Fn(&SomeBoardPos, u64) -> u64,
    calculate_attacks_for2: impl Fn(&SomeBoardPos2, u64) -> u64,
) -> u64 {
    calculate_attacks_for(&SomeBoardPos, 0) + calculate_attacks_for2(&SomeBoardPos2, 0)
}

fn main() {
    println!("{}", generate_all_possible_attacks_for(calculate_rook_attacks_for, calculate_rook_attacks_for));
}

Playground

Upon reading the answer I realized I'd have to specify the type at some point. I'm not sure what to do in the following case though:

Name the type instead of using impl BoardPos:

fn generate_magic_number_for<T: BoardPos>(pos: &T, piece: Piece) -> u64 {
    let idx = pos.idx();

    let (relevant_moves, number_of_relevant_moves, get_attacks_for): (
        u64,
        u64,
        fn(&T, u64) -> u64,
    ) = match piece {
        Piece::Bishop => (
            RELEVANT_BISHOP_MOVES_PER_SQUARE[idx],
            NUMBER_OF_RELEVANT_BISHOP_MOVES_PER_SQUARE[idx],
            piece::calculate_bishop_attacks_for,
        ),
        Piece::Rook => (
            RELEVANT_ROOK_MOVES_PER_SQUARE[idx],
            NUMBER_OF_RELEVANT_ROOK_MOVES_PER_SQUARE[idx],
            piece::calculate_rook_attacks_for,
        ),
        _ => panic!(
            "this function is only callable for bishops and rooks, was called with '{:?}'",
            piece
        ),
    };
    /// ...
}

Upvotes: 1

Finomnis
Finomnis

Reputation: 22476

If you look at this code:

trait BoardPos {}

fn calculate_rook_attacks_for(pos: &impl BoardPos, blockers: u64) -> u64 {
    unimplemented!()
}

fn generate_all_possible_attacks_for<T>(calculate_attacks_for: fn(&T, u64) -> u64)
where
    T: BoardPos,
{
    // cool generation stuff
}

fn main() {
    generate_all_possible_attacks_for(calculate_rook_attacks_for);
}

It makes sense that it complains about not knowing the type T, because neither generate_all_possible_attacks_for nor calculate_rook_attacks_for specify a type T. Both of them say that they need a type T with certain properties, but when you call the function, it needs to resolve to a concrete type that the compiler can actually compile to.

At some point, you actually need to specify what the exact type &impl BoardPos is supposed to be. For example here:

trait BoardPos {}
struct MyBoardPos;
impl BoardPos for MyBoardPos {}

fn calculate_rook_attacks_for(pos: &impl BoardPos, blockers: u64) -> u64 {
    unimplemented!()
}

fn generate_all_possible_attacks_for<T>(calculate_attacks_for: fn(&T, u64) -> u64)
where
    T: BoardPos,
{
    // cool generation stuff
}

fn main() {
    generate_all_possible_attacks_for::<MyBoardPos>(calculate_rook_attacks_for);
}

or here:

trait BoardPos {}
struct MyBoardPos;
impl BoardPos for MyBoardPos {}

fn calculate_rook_attacks_for(pos: &impl BoardPos, blockers: u64) -> u64 {
    unimplemented!()
}

fn generate_all_possible_attacks_for(calculate_attacks_for: fn(&MyBoardPos, u64) -> u64) {
    // cool generation stuff
}

fn main() {
    generate_all_possible_attacks_for(calculate_rook_attacks_for);
}

If you want to keep the &impl BoardPos in the function type, to call calculate_attacks_for inside if generate_all_possible_attacks_for several times with different types, I think you are out of luck. You cannot pass a generic function into another function. At the point where you pass it into the function, it needs to be a function pointer. Meaning, a real function that rust actually generated as bytecode. This means all generics have to be resolved, because I don't think the concept of an abstract function pointer exists. (but of course I might be wrong, please correct me)

Upvotes: 3

Related Questions