1

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
        ),
    };
    /// ...
}
1
  • 1
    I've edited my answer to cover your update. Commented Jun 6, 2022 at 11:24

2 Answers 2

3

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)

Sign up to request clarification or add additional context in comments.

3 Comments

"You cannot pass a generic function into another function" You can, but only when it's generic over lifetimes, not types doc.rust-lang.org/nomicon/hrtb.html
What to do if generate_all_possible_attacks_for receives the pos as &impl BoardPos as well? I've added something to the question.
@Elias Then you will probably have to introduce a generic: play.rust-lang.org/…
1

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
        ),
    };
    /// ...
}

2 Comments

Surely the whole point is to keep generate_all_possible_attacks_for generic? Which one can do, provided the type is known at the call site: play.rust-lang.org/…
"Surely the whole point is to keep generate_all_possible_attacks_for generic?" It depends on the actual desired implementation. But yes, what you suggest is another possibility, probably even more likely than mine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.