Reputation: 4112
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 infn
.
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 functiongenerate_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)
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
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));
}
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));
}
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
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