尝试在 Rust 中实现 sscanf,将 &str 作为参数传递时失败
Attempt to implement sscanf in Rust, failing when passing &str as argument
问题:
我是 Rust 的新手,我正在尝试实现一个从 C 模拟 sscanf 的宏。
到目前为止,它适用于任何数字类型,但不适用于字符串,因为我已经在尝试解析字符串。
macro_rules! splitter {
( $string:expr, $sep:expr) => {
let mut iter:Vec<&str> = $string.split($sep).collect();
iter
}
}
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
$x = res[i].parse::<$y>().unwrap_or_default();
i+=1;
)*
};
}
fn main() {
let mut a :u8; let mut b :i32; let mut c :i16; let mut d :f32;
let buffer = "00:98;76,39.6";
let sep = [':',';',','];
scan_to_types!(buffer,sep,[u8,i32,i16,f32],a,b,c,d); // this will work
println!("{} {} {} {}",a,b,c,d);
}
这显然行不通,因为在编译时,它会尝试将字符串切片解析为 str:
let a :u8; let b :i32; let c :i16; let d :f32; let e :&str;
let buffer = "02:98;abc,39.6";
let sep = [':',';',','];
scan_to_types!(buffer,sep,[u8,i32,&str,f32],a,b,e,d);
println!("{} {} {} {}",a,b,e,d);
$x = res[i].parse::<$y>().unwrap_or_default();
| ^^^^^ the trait `FromStr` is not implemented for `&str`
我试过的
我尝试使用 TypeId 和宏内部的 if else 条件来比较类型以跳过解析,但同样的情况发生了,因为它不会扩展为有效代码:
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
if TypeId::of::<$y>() == TypeId::of::<&str>(){
$x = res[i];
}else{
$x = res[i].parse::<$y>().unwrap_or_default();
}
i+=1;
)*
};
}
有没有办法在宏中设置条件或跳过重复?或者,是否有更好的方法来使用宏构建 sscanf ?我已经创建了解析这些字符串的函数,但我无法将类型作为参数传递,或使它们通用。
答案前的注释: 你可能不想在 Rust 中模拟 sscanf()
。 Rust 中有许多非常强大的解析器,因此您可能应该使用其中之一。
简单的答案:解决您的问题的最简单方法是将 &str
的使用替换为 String
,这使您的宏 compile and run。如果您的代码不是 performance-critical,那可能就是您所需要的。如果您关心性能和避免分配,请继续阅读。
String
的一个缺点是它在底层复制 来自您正在扫描的字符串的字符串数据到一个新分配的拥有的字符串中。您使用 &str
的原始方法应该允许您的 &str
直接指向扫描的数据,而无需任何复制。理想情况下,我们想写这样的东西:
trait MyFromStr {
fn my_from_str(s: &str) -> Self;
}
// when called on a type that impls `FromStr`, use `parse()`
impl<T: FromStr + Default> MyFromStr for T {
fn my_from_str(s: &str) -> T {
s.parse().unwrap_or_default()
}
}
// when called on &str, just return it without copying
impl MyFromStr for &str {
fn my_from_str(s: &str) -> &str {
s
}
}
不幸的是,它没有编译,抱怨“&str
的特征 MyFromStr
的实现冲突”,尽管这两个实现之间没有冲突,因为 &str
没有实现 FromStr
。但是 Rust 目前的工作方式,特征的一揽子实现排除了相同特征的手动实现,即使是在一揽子实现未涵盖的类型上也是如此。
将来这将由 David Tolnay 发明的 specialization. Specialization is not yet part of stable Rust, and might not come to stable Rust for years, so we have to think of another solution. In case of macro usage, we can just let the compiler "specialize" for us by creating two traits with the same name. (This is similar to the autoref-based specialization 解决,但更简单,因为它不需要 autoref 解析来工作,因为我们有明确提供的类型。)
我们为已解析和未解析的值创建单独的特征,并根据需要实现它们:
trait ParseFromStr {
fn my_from_str(s: &str) -> Self;
}
impl<T: FromStr + Default> ParseFromStr for T {
fn my_from_str(s: &str) -> T {
s.parse().unwrap_or_default()
}
}
pub trait StrFromStr {
fn my_from_str(s: &str) -> &str;
}
impl StrFromStr for &str {
fn my_from_str(s: &str) -> &str {
s
}
}
然后在宏中我们只要调用<$y>::my_from_str()
,让编译器生成正确的代码。因为宏是无类型的,所以这是有效的,因为我们永远不需要提供一个单一的“特征界限”来消除我们想要的 my_from_str()
的歧义。 (这样的特征界限需要专门化。)
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
#[allow(unused_assignments)]
{
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
$x = <$y>::my_from_str(&res[i]);
i+=1;
)*
}
};
}
完整示例in the playground。
问题:
我是 Rust 的新手,我正在尝试实现一个从 C 模拟 sscanf 的宏。 到目前为止,它适用于任何数字类型,但不适用于字符串,因为我已经在尝试解析字符串。
macro_rules! splitter {
( $string:expr, $sep:expr) => {
let mut iter:Vec<&str> = $string.split($sep).collect();
iter
}
}
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
$x = res[i].parse::<$y>().unwrap_or_default();
i+=1;
)*
};
}
fn main() {
let mut a :u8; let mut b :i32; let mut c :i16; let mut d :f32;
let buffer = "00:98;76,39.6";
let sep = [':',';',','];
scan_to_types!(buffer,sep,[u8,i32,i16,f32],a,b,c,d); // this will work
println!("{} {} {} {}",a,b,c,d);
}
这显然行不通,因为在编译时,它会尝试将字符串切片解析为 str:
let a :u8; let b :i32; let c :i16; let d :f32; let e :&str;
let buffer = "02:98;abc,39.6";
let sep = [':',';',','];
scan_to_types!(buffer,sep,[u8,i32,&str,f32],a,b,e,d);
println!("{} {} {} {}",a,b,e,d);
$x = res[i].parse::<$y>().unwrap_or_default();
| ^^^^^ the trait `FromStr` is not implemented for `&str`
我试过的
我尝试使用 TypeId 和宏内部的 if else 条件来比较类型以跳过解析,但同样的情况发生了,因为它不会扩展为有效代码:
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
if TypeId::of::<$y>() == TypeId::of::<&str>(){
$x = res[i];
}else{
$x = res[i].parse::<$y>().unwrap_or_default();
}
i+=1;
)*
};
}
有没有办法在宏中设置条件或跳过重复?或者,是否有更好的方法来使用宏构建 sscanf ?我已经创建了解析这些字符串的函数,但我无法将类型作为参数传递,或使它们通用。
答案前的注释: 你可能不想在 Rust 中模拟 sscanf()
。 Rust 中有许多非常强大的解析器,因此您可能应该使用其中之一。
简单的答案:解决您的问题的最简单方法是将 &str
的使用替换为 String
,这使您的宏 compile and run。如果您的代码不是 performance-critical,那可能就是您所需要的。如果您关心性能和避免分配,请继续阅读。
String
的一个缺点是它在底层复制 来自您正在扫描的字符串的字符串数据到一个新分配的拥有的字符串中。您使用 &str
的原始方法应该允许您的 &str
直接指向扫描的数据,而无需任何复制。理想情况下,我们想写这样的东西:
trait MyFromStr {
fn my_from_str(s: &str) -> Self;
}
// when called on a type that impls `FromStr`, use `parse()`
impl<T: FromStr + Default> MyFromStr for T {
fn my_from_str(s: &str) -> T {
s.parse().unwrap_or_default()
}
}
// when called on &str, just return it without copying
impl MyFromStr for &str {
fn my_from_str(s: &str) -> &str {
s
}
}
不幸的是,它没有编译,抱怨“&str
的特征 MyFromStr
的实现冲突”,尽管这两个实现之间没有冲突,因为 &str
没有实现 FromStr
。但是 Rust 目前的工作方式,特征的一揽子实现排除了相同特征的手动实现,即使是在一揽子实现未涵盖的类型上也是如此。
将来这将由 David Tolnay 发明的 specialization. Specialization is not yet part of stable Rust, and might not come to stable Rust for years, so we have to think of another solution. In case of macro usage, we can just let the compiler "specialize" for us by creating two traits with the same name. (This is similar to the autoref-based specialization 解决,但更简单,因为它不需要 autoref 解析来工作,因为我们有明确提供的类型。)
我们为已解析和未解析的值创建单独的特征,并根据需要实现它们:
trait ParseFromStr {
fn my_from_str(s: &str) -> Self;
}
impl<T: FromStr + Default> ParseFromStr for T {
fn my_from_str(s: &str) -> T {
s.parse().unwrap_or_default()
}
}
pub trait StrFromStr {
fn my_from_str(s: &str) -> &str;
}
impl StrFromStr for &str {
fn my_from_str(s: &str) -> &str {
s
}
}
然后在宏中我们只要调用<$y>::my_from_str()
,让编译器生成正确的代码。因为宏是无类型的,所以这是有效的,因为我们永远不需要提供一个单一的“特征界限”来消除我们想要的 my_from_str()
的歧义。 (这样的特征界限需要专门化。)
macro_rules! scan_to_types {
($buffer:expr,$sep:expr,[$($y:ty),+],$($x:expr),+) => {
#[allow(unused_assignments)]
{
let res = splitter!($buffer,$sep);
let mut i = 0;
$(
$x = <$y>::my_from_str(&res[i]);
i+=1;
)*
}
};
}
完整示例in the playground。