为什么我可以 return 使用“char.to_ascii_lowercase()”但不能使用“str.to_lowercase()”的自有值

Why can I return an owned value which uses 'char.to_ascii_lowercase()` but not with `str.to_lowercase()`

在接受 &str 和 returns impl Iterator<Item = char> 的函数中,我试图将输入转换为小写,然后过滤并映射该小写形式的字符。在使用 str.to_lowercase():

时,我被以下错误卡住了一段时间
  --> src/lib.rs                                                                                                                      
   |                                                                                                                                        
   |        cipher                                                                                                                          
   |   _____^                                                                                                                               
   |  |_____|                                                                                                                               
   | ||                                                                                                                                     
   | ||         .to_lowercase()                                                                                                             
   | ||_______________________- temporary value created here                                                                                
   | |          .chars()                                                                                                                    
   | |          .filter(|c| c.is_alphanumeric() && c.is_ascii())                                                                            
...  |                                                                                                                                      
   | |              }                                                                                                                       
   | |          })                                                                                                                          
   | |___________^ returns a value referencing data owned by the current function    

原始形式的函数:

pub fn decode_to_iter(cipher: &str) -> impl Iterator<Item = char> {
    cipher
        .to_lowercase()
        .chars()
        .filter(|c| c.is_alphanumeric() && c.is_ascii())
        .map(|c| {
            if c.is_alphabetic() {
                (((b'z' - (c as u8)) + b'a') as char)
            } else {
                c
            }
        })
}

我在网上遇到了几个非常相似的问题,询问如何 return 已使用 .to_lowercase() 转换的拥有值,但 none 发布的解决方案对我有用.

我尽量避免使用 &char 并在我的 return 类型中坚持使用 char

我曾尝试使用 .to_owned() 之类的函数来获取引用的所有权,但最终还是空手而归。

最终,我能够使用 char.to_ascii_lowercase() 编译并通过我的测试。我函数的工作版本是:

pub fn decode_to_iter<'a>(cipher: &'a str) -> impl Iterator<Item = char> + 'a {
    cipher
        .chars()
        .filter(|c| c.is_alphanumeric() && c.is_ascii())
        .map(|c| {
            if c.is_alphabetic() {
                (((b'z' - (c.to_ascii_lowercase() as u8)) + b'a') as char)
            } else {
                c.to_ascii_lowercase()
            }
        })
}

最让我困惑的事情之一是 str.to_lowercase()char.to_ascii_lowercase() 之间的区别。 Primative Type Char.to_ascii_lowercase() 的文档显示:

pub fn to_ascii_lowercase(&self) -> char

Primative Type Str.to_lowercase() 的文档显示:

pub fn to_lowercase(&self) -> String

除非我误解了,否则这两个函数似乎 return 一个拥有的值所以我不确定为什么只有 char.to_ascii_lowercase() 有效。

我在想:

  1. 如何正确 return 使用 .to_lowercase() 而不是 .to_ascsii_lowercase()Impl Iterator 值?

  2. char.to_lowercase()str.to_ascii_lowercase()有什么区别?

这里的问题是 str::to_lowercase allocates a new String value as the lowercased version of your string, and then the str::chars method borrows from that new String value. (You can tell it borrows from the String value by looking at the std::str::Chars 结构,它有一个生命周期参数,指向它正在迭代其字符的字符串。)

那么为什么会出现问题呢?好吧,由 to_lowercase 分配的 String 值是作为迭代器链的一部分创建的临时值,它又在函数作用域的末尾被丢弃(编译器的错误消息应该告诉你这一点) .因此,编译器会阻止您遇到释放后使用错误。如果它让你 return 迭代器,那么它将允许调用者从已被释放的 String 中读取,这违反了内存安全。

您使用 char::to_ascii_lowercase 的变体有效,因为您从不分配中间 String 值。因此,您最终 returning 了一个从输入借用到函数的迭代器,这是有效的,也是您需要添加生命周期参数的原因。 (否则,编译器假定 impl Trait 的生命周期是 'static,这里不是这种情况。你的 returned 值的生命周期是 tied 到函数输入的生命周期。)

您可以通过避免分配临时 String 来解决此问题,这有望提高效率。诀窍是要意识到 char 有一个方法 char::to_lowercase ,其中 return 是一个 迭代器 在给定字符的小写等价物上,并且 不是一个String。因此,您可以直接从这里阅读:

pub fn decode_to_iter<'a>(cipher: &'a str) -> impl Iterator<Item = char> + 'a {
    cipher
        .chars()
        .flat_map(|c| c.to_lowercase())
        .filter(|c| c.is_alphanumeric() && c.is_ascii())
        .map(|c| {
            if c.is_alphabetic() {
                (((b'z' - (c as u8)) + b'a') as char)
            } else {
                c
            }
        })
}

这里唯一真正的技巧是使用 flat_map,它类似于普通的 map,但它可以让你 return 一个迭代器,然后将其展平为原始迭代器 (如果你在这里使用普通的 map,你最终会得到一个迭代器的迭代器)。

话虽如此,如果您真的只关心这里的 ASCII 代码点(由于您的 filter 谓词),那么您不需要完整的 Unicode 感知小写机制。所以我可能会像你的第二个变体一样写它, char::to_ascii_lowercase:

pub fn decode_to_iter<'a>(cipher: &'a str) -> impl Iterator<Item = char> + 'a {
    cipher
        .chars()
        .filter(|c| c.is_ascii_alphanumeric())
        .map(|c| c.to_ascii_lowercase())
        .map(|c| {
            if c.is_alphabetic() {
                (((b'z' - (c as u8)) + b'a') as char)
            } else {
                c
            }
        })
}

这是显示代码的 playground link