为什么我可以使用 `char.to_ascii_lowercase()` 返回一个拥有的值,但不能用 `str.to_lowercase()` 呢?

4

在一个接收&str并返回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
            }
        })
}

我在网上看到了一些关于如何返回使用.to_lowercase()转换过的拥有值的类似问题的提问,但是所有已发布的解决方案都不能满足我的需求。
我试图避免使用&char并坚持在我的返回类型中使用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() 之间的区别。根据 字符原始类型 下的文档,.to_ascii_lowercase() 的显示:

pub fn to_ascii_lowercase(&self) -> char

基本类型 Str 下,对于 .to_lowercase() 的文档显示:

pub fn to_lowercase(&self) -> String

除非我误解了,这两个函数似乎都会返回一个所有权值,所以我不确定为什么只有char.to_ascii_lowercase()有效。

我在想:

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

  2. char.to_lowercase()str.to_ascii_lowercase()之间的区别是什么?


这是因为chars()借用了字符串。 - Boiethios
你的字符串是否只包含 ASCII 字符? - Boiethios
1个回答

4
问题在于 str::to_lowercase 分配了一个新的 String 值作为你字符串的小写版本,然后 str::chars 方法从该新 String 值中借用。 (通过查看具有引用其正在迭代字符的字符串的生命周期参数的std::str::Chars 结构,可以知道它从 String 值借用)。
那么这里有什么问题呢? 好吧,由 to_lowercase 分配的那个 String 值是作为您迭代器链的一部分创建的临时值,而该链又在函数作用域的末尾被丢弃(编译器的错误消息应该告诉您这一点)。 因此,编译器防止您出现使用已释放内存的 bug。 如果允许您返回迭代器,则会允许调用者从已释放的 String 中读取数据,这违反了内存安全性。
使用 char::to_ascii_lowercase 的变体有效是因为您从未分配过那个中间的 String 值。 因此,您最终返回了一个借用自函数输入的迭代器,这是有效的,并且这就是为什么需要添加生命周期参数的原因。(否则,编译器会假定 impl Trait 上的生命周期为 'static,而这在这里不是这种情况。返回值的生命周期与函数输入的生命周期是 关联 的)。
您可以通过避免临时 String 的分配来解决这个问题,这样应该更有效率。 诀窍在于意识到 char 具有一个方法char::to_lowercase,它返回给定字符的小写等效项的一个迭代器,而不是一个 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,但可以返回一个迭代器,然后将其展开到原始迭代器中(如果你在这里使用普通的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链接,显示了代码。


感谢 @BurntSushi5 的解释和指出使用 flat_map 的用法。你提到了避免使用 char 小写方法产生不必要的字符串分配,这是一个很好的观点,但我仍然想知道是否可能在迭代器链中拥有 str::to_lowercase 的临时字符串所有权,以避免在函数作用域结束时将其丢弃? - AC-5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接