默认泛型参数
Default generic parameter
我有一个可以使用构建器模式构建的结构,因为有一些 Option
al 字段。
如果我使用构建器函数指定这些可选字段,则不必指定通用参数。
但是如果我不调用这些函数,我需要指定泛型参数。
这是一个例子:
use Structs::*;
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T, F: Fn(T)> Struct<T, F> {
fn new(value: T) -> Struct<T, F> {
Struct {
func: None,
value: value,
}
}
fn build(self) -> Structs<T, F> {
if let Some(func) = self.func {
Struct2(self.value, func)
}
else {
Struct1(self.value)
}
}
fn func(mut self, func: F) -> Struct<T, F> {
self.func = Some(func);
self
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| { println!("{}", n); })
.build();
//let _strct = Struct::new(42).build(); // Does not compile.
let _strct = Struct::<_, &Fn(_)>::new(42).build();
}
我想在未设置可选字段时省略类型注释,如下所示:
let _strct = Struct::new(42).build();
需要说明的是F
类型依赖T
.
我试过这样指定默认类型参数:
impl<T, F: Fn(T) = Box<Fn(T)>> Struct<T, F> {
但这并没有解决问题。
那么我怎样才能避免在 Struct::new()
调用中指定类型参数呢?
如果无法避免这种情况,是否有任何替代构建器模式的方法可以让我省略类型注释?
有一种方法可以通过改变构建器的类型来解决这个问题。由于 Struct::func
拥有构建器的所有权,并且 returns 拥有新的构建器,我们可以自由更改结果类型。
首先,我们需要为F
指定一个初始类型。我们可以为 Fn(T)
选择任何现有的实现,但我们可以做得更好。我建议我们使用一个 empty/void/uninhabited/bottom type,这样很明显当 F
是那种类型时,那么 Option
就是 None
(你不能构造一个 Some(x)
因为空类型没有有效的 x
)。这种方法的一个缺点是为类型(闭包除外)实现 Fn
、FnMut
和 FnOnce
是不稳定的,需要夜间编译器。
#![feature(fn_traits)]
#![feature(unboxed_closures)]
enum Void {}
impl<T> FnOnce<T> for Void {
type Output = ();
extern "rust-call" fn call_once(self, _args: T) {
match self {}
}
}
impl<T> FnMut<T> for Void {
extern "rust-call" fn call_mut(&mut self, _args: T) {
match *self {}
}
}
impl<T> Fn<T> for Void {
extern "rust-call" fn call(&self, _args: T) {
match *self {}
}
}
接下来,让我们将 Struct::new
移动到另一个 impl
区块:
impl<T> Struct<T, Void> {
fn new(value: T) -> Struct<T, Void> {
Struct {
func: None,
value: value,
}
}
}
这个 impl
不是 F
上的泛型:new
只会生成一个 Struct
,其中 F = Void
.这避免了在从未调用 func
的情况下出现歧义。
最后,我们需要func
更改生成器的类型:
impl<T, F0: Fn(T)> Struct<T, F0> {
fn func<F1: Fn(T)>(self, func: F1) -> Struct<T, F1> {
Struct {
func: Some(func),
value: self.value,
}
}
}
此方法需要保留在 impl
块中,该块在 Struct<T, F>
上的 F
类型参数上是通用的,以便它可以用于 func
已经调用。但是,func
本身也必须是通用的,以便它可以接收任何类型的函数(而不是匹配构建器类型的函数)。然后,我们必须构建一个新的 Struct
而不是变异 self
,因为我们不能将 Struct<T, F0>
强制转换为 Struct<T, F1>
.
继 之后,这里有一个类似的想法可以在稳定的 Rust 上运行:
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T> Struct<T, fn(T)> {
fn new(value: T) -> Struct<T, fn(T)> {
Struct {
func: None,
value: value,
}
}
}
impl<T, F: Fn(T)> Struct<T, F> {
fn func<F2: Fn(T)>(self, func: F2) -> Struct<T, F2> {
Struct {
func: Some(func),
value: self.value,
}
}
fn build(self) -> Structs<T, F> {
use Structs::*;
if let Some(func) = self.func {
Struct2(self.value, func)
} else {
Struct1(self.value)
}
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| {
println!("{}", n);
})
.build();
let _strct = Struct::new(42).build();
}
而不是明确的 Void
类型,我们只是说我们 return 一个将被参数化为函数指针的结构。您同样可以指定一个参考特征对象:
impl<T> Struct<T, &'static Fn(T)> {
fn new(value: T) -> Struct<T, &'static Fn(T)> {
根据评论回答我自己的问题:
If you don't specify a concrete type for T
or F
, then how much space should the Rust compiler allocate to store Struct
?
fn()
的大小在 64 位机器上是 8 个字节,导致整个结构总共有 16 个字节:
std::mem::size_of::<fn()>();
std::mem::size_of_val(&strct);
但是,当你给它一个具体的回调时,这个结构只需要8个字节!那是因为回调的类型会单态化,不需要状态,基本上可以内联。
Francis Gagné 的解决方案在每种情况下只需要 8 个字节,因为 Void
类型的大小为零!
我有一个可以使用构建器模式构建的结构,因为有一些 Option
al 字段。
如果我使用构建器函数指定这些可选字段,则不必指定通用参数。
但是如果我不调用这些函数,我需要指定泛型参数。
这是一个例子:
use Structs::*;
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T, F: Fn(T)> Struct<T, F> {
fn new(value: T) -> Struct<T, F> {
Struct {
func: None,
value: value,
}
}
fn build(self) -> Structs<T, F> {
if let Some(func) = self.func {
Struct2(self.value, func)
}
else {
Struct1(self.value)
}
}
fn func(mut self, func: F) -> Struct<T, F> {
self.func = Some(func);
self
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| { println!("{}", n); })
.build();
//let _strct = Struct::new(42).build(); // Does not compile.
let _strct = Struct::<_, &Fn(_)>::new(42).build();
}
我想在未设置可选字段时省略类型注释,如下所示:
let _strct = Struct::new(42).build();
需要说明的是F
类型依赖T
.
我试过这样指定默认类型参数:
impl<T, F: Fn(T) = Box<Fn(T)>> Struct<T, F> {
但这并没有解决问题。
那么我怎样才能避免在 Struct::new()
调用中指定类型参数呢?
如果无法避免这种情况,是否有任何替代构建器模式的方法可以让我省略类型注释?
有一种方法可以通过改变构建器的类型来解决这个问题。由于 Struct::func
拥有构建器的所有权,并且 returns 拥有新的构建器,我们可以自由更改结果类型。
首先,我们需要为F
指定一个初始类型。我们可以为 Fn(T)
选择任何现有的实现,但我们可以做得更好。我建议我们使用一个 empty/void/uninhabited/bottom type,这样很明显当 F
是那种类型时,那么 Option
就是 None
(你不能构造一个 Some(x)
因为空类型没有有效的 x
)。这种方法的一个缺点是为类型(闭包除外)实现 Fn
、FnMut
和 FnOnce
是不稳定的,需要夜间编译器。
#![feature(fn_traits)]
#![feature(unboxed_closures)]
enum Void {}
impl<T> FnOnce<T> for Void {
type Output = ();
extern "rust-call" fn call_once(self, _args: T) {
match self {}
}
}
impl<T> FnMut<T> for Void {
extern "rust-call" fn call_mut(&mut self, _args: T) {
match *self {}
}
}
impl<T> Fn<T> for Void {
extern "rust-call" fn call(&self, _args: T) {
match *self {}
}
}
接下来,让我们将 Struct::new
移动到另一个 impl
区块:
impl<T> Struct<T, Void> {
fn new(value: T) -> Struct<T, Void> {
Struct {
func: None,
value: value,
}
}
}
这个 impl
不是 F
上的泛型:new
只会生成一个 Struct
,其中 F = Void
.这避免了在从未调用 func
的情况下出现歧义。
最后,我们需要func
更改生成器的类型:
impl<T, F0: Fn(T)> Struct<T, F0> {
fn func<F1: Fn(T)>(self, func: F1) -> Struct<T, F1> {
Struct {
func: Some(func),
value: self.value,
}
}
}
此方法需要保留在 impl
块中,该块在 Struct<T, F>
上的 F
类型参数上是通用的,以便它可以用于 func
已经调用。但是,func
本身也必须是通用的,以便它可以接收任何类型的函数(而不是匹配构建器类型的函数)。然后,我们必须构建一个新的 Struct
而不是变异 self
,因为我们不能将 Struct<T, F0>
强制转换为 Struct<T, F1>
.
继
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T> Struct<T, fn(T)> {
fn new(value: T) -> Struct<T, fn(T)> {
Struct {
func: None,
value: value,
}
}
}
impl<T, F: Fn(T)> Struct<T, F> {
fn func<F2: Fn(T)>(self, func: F2) -> Struct<T, F2> {
Struct {
func: Some(func),
value: self.value,
}
}
fn build(self) -> Structs<T, F> {
use Structs::*;
if let Some(func) = self.func {
Struct2(self.value, func)
} else {
Struct1(self.value)
}
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| {
println!("{}", n);
})
.build();
let _strct = Struct::new(42).build();
}
而不是明确的 Void
类型,我们只是说我们 return 一个将被参数化为函数指针的结构。您同样可以指定一个参考特征对象:
impl<T> Struct<T, &'static Fn(T)> {
fn new(value: T) -> Struct<T, &'static Fn(T)> {
根据评论回答我自己的问题:
If you don't specify a concrete type for
T
orF
, then how much space should the Rust compiler allocate to storeStruct
?
fn()
的大小在 64 位机器上是 8 个字节,导致整个结构总共有 16 个字节:
std::mem::size_of::<fn()>();
std::mem::size_of_val(&strct);
但是,当你给它一个具体的回调时,这个结构只需要8个字节!那是因为回调的类型会单态化,不需要状态,基本上可以内联。
Francis Gagné 的解决方案在每种情况下只需要 8 个字节,因为 Void
类型的大小为零!