通过特征而不是结构参数化变量?
Variable parameterised over a trait not a struct?
我正在努力研究 Rust 的泛型。我正在写一些东西来从不同的网站提取 HTML。我想要的是这样的:
trait CanGetTitle {
fn get_title(&self) -> String;
}
struct Spider<T: CanGetTitle> {
pub parser: T
}
struct GoogleParser;
impl CanGetTitle for GoogleParser {
fn get_title(&self) -> String {
"title from H1".to_string().clone()
}
}
struct YahooParser;
impl CanGetTitle for YahooParser {
fn get_title(&self) -> String {
"title from H2".to_string().clone()
}
}
enum SiteName {
Google,
Yahoo,
}
impl SiteName {
fn from_url(url: &str) -> SiteName {
SiteName::Google
}
}
fn main() {
let url = "http://www.google.com";
let site_name = SiteName::from_url(&url);
let spider: Spider<_> = match site_name {
Google => Spider { parser: GoogleParser },
Yahoo => Spider { parser: YahooParser }
};
spider.parser.get_title(); // fails
}
我收到关于 match
returning Spider
参数化两种不同类型的错误。它期望它 return Spider<GoogleParser>
因为那是模式匹配的第一臂的 return 类型。
如何声明 spider
应该是任何 Spider<T: CanGetTitle>
?
How can I declare that spider
should be any Spider<T: CanGetTitle>
?
你不能。简而言之,编译器不知道要分配多少 space 来存储 spider
在堆栈上。
相反,您需要使用 trait object: Box<CanGetTitle>
:
impl<T: ?Sized> CanGetTitle for Box<T>
where
T: CanGetTitle,
{
fn get_title(&self) -> String {
(**self).get_title()
}
}
fn main() {
let innards: Box<CanGetTitle> = match SiteName::Google {
SiteName::Google => Box::new(GoogleParser),
SiteName::Yahoo => Box::new(YahooParser),
};
let spider = Spider { parser: innards };
}
How can I declare that spider
should be any Spider<T: CanGetTitle>
?
补充一点@Shepmaster 已经说过的内容,spider
不能是 any Spider<T>
,因为它必须正好是 一个Spider<T>
。 Rust 使用单态化(已解释 here)实现泛型,这意味着它为使用的每个具体类型编译一个单独版本的多态函数。如果编译器无法为特定调用站点推断出唯一的 T
,那么这是一个编译错误。在您的情况下,编译器推断类型必须是 Spider<Google>
,但下一行尝试将其视为 Spider<Yahoo>
.
使用特征对象可以让您将所有这些推迟到运行时。通过将实际对象存储在堆上并使用 Box
,编译器知道需要分配多少 space 堆栈(只是 Box
的大小)。但这会带来性能成本:当需要访问数据时会有额外的指针间接,更重要的是,优化编译器无法内联虚拟调用。
通常可以重新调整事物,以便您无论如何都可以使用单态类型。在您的情况下,一种方法是避免临时分配给多态变量,并仅在您知道其具体类型的地方使用该值:
fn do_stuff<T: CanGetTitle>(spider: Spider<T>) {
println!("{:?}", spider.parser.get_title());
}
fn main() {
let url = "http://www.google.com";
let site_name = SiteName::from_url(&url);
match site_name {
SiteName::Google => do_stuff(Spider { parser: GoogleParser }),
SiteName::Yahoo => do_stuff(Spider { parser: YahooParser })
};
}
请注意,每次调用 do_stuff
时,T
都会解析为不同的类型。您只编写了 do_stuff
的一个实现,但编译器将其单态化了两次 - 对于您调用它的每种类型一次。
如果您使用 Box
,则必须在 Box
的 vtable 中查找对 parser.get_title()
的每次调用。但是这个版本通常会更快,因为它避免了查找的需要,并允许编译器在每种情况下内联 parser.get_title()
的主体。
我正在努力研究 Rust 的泛型。我正在写一些东西来从不同的网站提取 HTML。我想要的是这样的:
trait CanGetTitle {
fn get_title(&self) -> String;
}
struct Spider<T: CanGetTitle> {
pub parser: T
}
struct GoogleParser;
impl CanGetTitle for GoogleParser {
fn get_title(&self) -> String {
"title from H1".to_string().clone()
}
}
struct YahooParser;
impl CanGetTitle for YahooParser {
fn get_title(&self) -> String {
"title from H2".to_string().clone()
}
}
enum SiteName {
Google,
Yahoo,
}
impl SiteName {
fn from_url(url: &str) -> SiteName {
SiteName::Google
}
}
fn main() {
let url = "http://www.google.com";
let site_name = SiteName::from_url(&url);
let spider: Spider<_> = match site_name {
Google => Spider { parser: GoogleParser },
Yahoo => Spider { parser: YahooParser }
};
spider.parser.get_title(); // fails
}
我收到关于 match
returning Spider
参数化两种不同类型的错误。它期望它 return Spider<GoogleParser>
因为那是模式匹配的第一臂的 return 类型。
如何声明 spider
应该是任何 Spider<T: CanGetTitle>
?
How can I declare that
spider
should be anySpider<T: CanGetTitle>
?
你不能。简而言之,编译器不知道要分配多少 space 来存储 spider
在堆栈上。
相反,您需要使用 trait object: Box<CanGetTitle>
:
impl<T: ?Sized> CanGetTitle for Box<T>
where
T: CanGetTitle,
{
fn get_title(&self) -> String {
(**self).get_title()
}
}
fn main() {
let innards: Box<CanGetTitle> = match SiteName::Google {
SiteName::Google => Box::new(GoogleParser),
SiteName::Yahoo => Box::new(YahooParser),
};
let spider = Spider { parser: innards };
}
How can I declare that
spider
should be anySpider<T: CanGetTitle>
?
补充一点@Shepmaster 已经说过的内容,spider
不能是 any Spider<T>
,因为它必须正好是 一个Spider<T>
。 Rust 使用单态化(已解释 here)实现泛型,这意味着它为使用的每个具体类型编译一个单独版本的多态函数。如果编译器无法为特定调用站点推断出唯一的 T
,那么这是一个编译错误。在您的情况下,编译器推断类型必须是 Spider<Google>
,但下一行尝试将其视为 Spider<Yahoo>
.
使用特征对象可以让您将所有这些推迟到运行时。通过将实际对象存储在堆上并使用 Box
,编译器知道需要分配多少 space 堆栈(只是 Box
的大小)。但这会带来性能成本:当需要访问数据时会有额外的指针间接,更重要的是,优化编译器无法内联虚拟调用。
通常可以重新调整事物,以便您无论如何都可以使用单态类型。在您的情况下,一种方法是避免临时分配给多态变量,并仅在您知道其具体类型的地方使用该值:
fn do_stuff<T: CanGetTitle>(spider: Spider<T>) {
println!("{:?}", spider.parser.get_title());
}
fn main() {
let url = "http://www.google.com";
let site_name = SiteName::from_url(&url);
match site_name {
SiteName::Google => do_stuff(Spider { parser: GoogleParser }),
SiteName::Yahoo => do_stuff(Spider { parser: YahooParser })
};
}
请注意,每次调用 do_stuff
时,T
都会解析为不同的类型。您只编写了 do_stuff
的一个实现,但编译器将其单态化了两次 - 对于您调用它的每种类型一次。
如果您使用 Box
,则必须在 Box
的 vtable 中查找对 parser.get_title()
的每次调用。但是这个版本通常会更快,因为它避免了查找的需要,并允许编译器在每种情况下内联 parser.get_title()
的主体。