处理 "expected fn pointer, found fn item"

Dealing with "expected fn pointer, found fn item"

我进行了大量 Google 搜索,试图弄清楚这一点。我有一个库,我希望人们能够传递对自定义“签名者”函​​数的引用(作为直接提供密钥的替代方法)。这是我最近遇到的问题:


mismatched types

expected fn pointer, found fn item

note: expected enum `std::option::Option<&for<'r, 's> fn(&'r [u8; 20], &'s [u8; 32]) -> &[u8; 32]>`
         found enum `std::option::Option<&for<'r, 's> fn(&'r [u8; 20], &'s [u8; 32]) -> &[u8; 32] {signer_func::<'_>}>`rustc(E0308)
check_handle.rs(28, 17): expected fn pointer, found fn item

我正在尝试将该函数传递给一个 struct,它像这样通过系统传递:

pub struct Params<'a> {
    pub signer: Option<&'a fn(address: &[u8; 20], data: &[u8; 32]) -> &'a[u8; 32]>,
}

我已经尝试按照我在大多数地方看到的建议在定义它的地方转换该函数,但编译器抱怨我无法转换非原始类型:

pub fn signer_func<'a>(address: &[u8; 20], data: &[u8; 32]) -> &'a[u8; 32] {
    . . .
}

. . .

let params = Params {
    signer: Option::from(&signer_func as &fn(&[u8; 20], &[u8; 32]) -> &[u8; 32])
};

我试过使用 Fn 特性并围绕它重新设计我的结构和函数,但一直卡在我需要在最终函数中包含特性的地方(我会得到错误,特性未定义)。

但这是我需要走的方向并专注于弄清楚为什么目标函数无法识别特征名称吗?感觉以我现在的方式使用 fn 是最直接的,“指针与项目”似乎应该是微不足道的克服,但我就是找不到它。

编辑

好的,我已经切换回使用 Fn 特性并拥有这个:

pub struct SignRequest {
    pub address: [u8; 20],
    pub data: [u8; 32]
}

pub struct Signer<CustomSigner>
where 
    CustomSigner: Fn(&SignRequest) -> [u8; 32],
{
    pub address: [u8; 20],
    pub data: [u8; 32],
    pub sign: CustomSigner
}

impl<CustomSigner> Signer<CustomSigner>
where
    CustomSigner: Fn(&SignRequest) -> [u8; 32],
{
    pub fn new(address: [u8; 20], data: [u8; 32], sign: CustomSigner) -> Signer<CustomSigner> {
        Signer { address, data, sign }
    }
}

pub struct SignaturesParams<CustomSigner: for<'r> std::ops::Fn(&'r SignRequest) -> [u8; 32]> {
    pub address: H160,
    pub private_key: Option<H256>,
    pub signer: Option<Signer<CustomSigner>>,
    pub data: [u8; 32],
}

pub async fn signatures<CustomSigner: for<'r> std::ops::Fn(&'r SignRequest) -> [u8; 32]>(params: &SignaturesParams<CustomSigner>) -> Result<Signatures, Box<dyn std::error::Error + Sync + Send>> {
 
. . . 
}

编译得很好(我的 IDE 建议并添加了疯狂寻找“寻找”的东西),但是当我尝试在另一个模块中使用 SignaturesParams 时,我 运行 成问题:

let signatures_params = SignaturesParams {
    address: params.eth_address.clone(),
    private_key: params.private_key.clone(),
    data: hash_message(serde_json::to_string(&message)?),
    signer: Option::None
};
type inside `async fn` body must be known in this context

cannot infer type for type parameter `CustomSigner` declared on the struct `SignaturesParams`rustc(E0698)
request.rs(34, 19): cannot infer type for type parameter `CustomSigner` declared on the struct `SignaturesParams`

我已经尝试了将该类型纳入范围的各种排列(例如,在那个 signatures_params 块的容器函数定义中包括那条长的“for”行)但没有找到正确的位置尚无法识别它。

我显然有些笨手笨脚,我现在的结构并不是最优的——我只是想弄清楚它们是如何连接在一起的。谢谢你的建议!

最终更新

我最终放弃了我试图工作的流程(即,预先定义一个闭包并将其传递给几个结构,最终由终止函数执行)。相反,我将这些步骤分解为更小的部分,这样我就可以独立地预构建消息,然后将这些消息和自定义闭包直接传递给 Signer 结构。

#[derive(Copy, Clone)]
pub struct SignData {
    pub address: [u8; 20],
    pub data: [u8; 32],
    pub private_key: Option<[u8; 32]>,
}

#[derive(Clone)]
pub struct Signature {
    pub data: String,
}

#[derive(Clone)]
pub struct Signer<F, Fut>
where
    F: Fn(SignData) -> Fut,
    Fut: Future<Output = Signature>,
{
    pub sign_func: F,
}

impl<F, Fut> Signer<F, Fut>
where
    F: Fn(SignData) -> Fut,
    Fut: Future<Output = Signature>,
{
    pub fn new(signer: F) -> Signer<F, Fut> {
        Signer { sign_func: signer }
    }

    pub fn sign(self, sign_data: SignData) -> Fut {
        (self.sign_func)(sign_data)
    }
}

然后可以以这种方式定义和使用与之一起使用的闭包。

let closure = async move |x: SignData| { 
. . .
    Signature { data: some_String } 
};

let signature = Signer::new(closure).sign(some_SignData).await.data

我认为目前这对我来说很管用。感谢您的帮助!

问题是你将太多生命周期绑定在一起。

pub signer: Option<&'a fn(address: &[u8; 20], data: &[u8; 32]) -> &'a[u8; 32]>

这需要一个函数的引用(它在生命周期 'a 中存在),该函数 returns 一个切片(也是生命周期 'a)。如果这些生命周期不兼容,则不会进行类型检查。考虑创建两个独立的生命周期。

pub struct Params<'a, 'b> {
  pub signer: Option<&'a fn(address: &[u8; 20], data: &[u8; 32]) -> &'b[u8; 32]>,
}

不过,正如您已经指出的那样,Fn 特征是更好的选择。你很少想借一个Fn,所以我建议signer的类型是

pub signer: Option<Box<dyn Fn(address: &[u8; 20], data: &[u8; 32]) -> &'a [u8; 32]>>

注意 Box 而不是 &,并且关键字 dyn 被推荐用于较新版本的 Rust 中的特征对象,总有一天会需要。

初始代码的问题在于,虽然您可以将 fn 项(编译器称为 fn ... {name})转换(或强制)为 fn 指针 (fn ...),但此 属性 不是递归的:您不能将对 fn 项的引用转换为对 fn 指针的引用。也就是说,signer_func as fn(&[u8; 20], &[u8; 32]) -> &'a [u8; 32] 有效,但 &signer_func as &fn(&[u8; 20], &[u8; 32]) -> &'a [u8; 32] 无效。解决方法很简单:投射,然后取参考。即&(signer_func as fn(&[u8; 20], &[u8; 32]) -> &'a [u8; 32]).

&fn() 看起来一点都不对,但是:fn() 已经是一个指针,所以 &fn() 是冗余的双重重定向。你可能想改变它。

你的第二个代码中的问题是编译器无法推断出 CustomSigner 的类型是什么,因为你提供了 None。您需要明确指定此类型参数。如果不查看代码,我无法判断它需要什么,但假设在这种情况下你真的不需要它,你可以指定一个虚拟类型,例如fn(&SignRequest) -> [u8; 32].