< 返回版块

wangshankun 发表于 2022-11-17 17:13

vector使用into_iter会发生所有权转移,但在下面这个例子里,teams.into_iter()之后,teams还能使用;

反而使用teams.iter()这种引用item的方式,会报错;这就有点奇怪了

use std::collections::HashMap;
fn main() {
    let teams = [
        ("Chinese Team", 100),
        ("American Team", 10),
        ("France Team", 50),
    ];

    let mut teams_map1 = HashMap::new();
    for team in &teams {
        teams_map1.insert(team.0, team.1);
    }

    let teams_map2 = teams.into_iter().collect();
    //let teams_map2 =teams.iter().cloned().collect();//这样可行
    
    //错误:
    //HashMap<&str, i32> cannot be built from std::iter::Iterator<Item=&(&str, i32)>
    //let teams_map2 =teams.iter().collect();
    
    assert_eq!(teams_map1, teams_map2);
    println!("Success! {:#?}",teams)
}

评论区

写评论
作者 wangshankun 2022-11-21 17:35

感谢大佬 .

--
👇
苦瓜小仔: > 入参是string情况下应该报错的,而rust自动加了一个引用,避免错误

嗯。这种自动引用和自动解引用只发生在 reciever type 上,即 self/&self/&mut self 这三种。

而且任何时候,方法和函数签名的所有参数最终必须严格匹配类型。所以某些增加人体工程性的规则,比如 reciever type 上自动(解)引用、其他参数的 coercion,则发生在查找实现的过程中 。

Clone::clone&T -> T 的,其方法定义就是那样 fn(&T) -> T。而 T -> T 意味着 fn(T) -> T

苦瓜小仔 2022-11-21 17:05

入参是string情况下应该报错的,而rust自动加了一个引用,避免错误

嗯。这种自动引用和自动解引用只发生在 reciever type 上,即 self/&self/&mut self 这三种。

而且任何时候,方法和函数签名的所有参数最终必须严格匹配类型。所以某些增加人体工程性的规则,比如 reciever type 上自动(解)引用、其他参数的 coercion,则发生在查找实现的过程中 。

Clone::clone&T -> T 的,其方法定义就是那样 fn(&T) -> T。而 T -> T 意味着 fn(T) -> T

作者 wangshankun 2022-11-21 15:52

感谢感谢,理解您的意思了; 因为&(&str, u8).clone 天然match了clone(&self) -> Self定义所以,就直接调用; 而String.clone,string无法匹配到&self,因此编译器自动加了一个&变成&string后match到clone(&self),才调用;

针对这两个类型的clone(&self) -> Self定义可以理解为下面函数的定义:

String::clone(&string)->String // 入参是string情况下应该报错的,而rust自动加了一个引用,避免错误

(&str,u8)::clone(&(&str,u8))->(&str,u8)//入参是&(&str,u8),正好匹配了函数定义

clone本身定义就是&T->T,但是因为初学时候用string和vec的clone次数太多,误以为clone是T->T的刻板印象;

再次感谢大佬!

苦瓜小仔 2022-11-21 13:48

为什么不是 <&(&str, u8) as Clone>::clone(&(&(&str, u8))) -> &(&str, u8)呢

我已经说过了,分析 val.clone() 的前提是 自动引用和解引用的方法调用规则

即 Rust 查找 candidate receiver type 的顺序:

  • T
  • &T
  • &mut T
  • *T
  • &*T
  • &mut *T
  • coercion to U
  • &U
  • &mut U
let t1: &(&str, u8) = &("", 1);

// <(&str, u8) as Clone>::clone(&(&str, u8)) -> (&str, u8) 有这个实现,所以调用
// 实现在这:impl<Ret, T> Clone for fn (T1, T2, …, Tn) -> Ret
let t2: (&str, u8) = t1.clone();

// <&(&str, u8) as Clone>::clone(&(&(&str, u8))) -> &(&str, u8) 有这个实现,所以调用
// 实现在这:impl<T> Clone for &T where T: ?Sized
let t3: &(&str, u8) = (&t1).clone();
let y1: (&str, u8) = ("", 1);

// <(&str, u8) as Clone>::clone((&str, u8)) -> (&str, u8) 没有这个实现,
// 所以添加一个对 receiver type 添加一个 &,
// <(&str, u8) as Clone>::clone(&(&str, u8)) -> (&str, u8) 有这个实现,所以调用
let y2: (&str, u8) = y1.clone();

// <(&str, u8) as Clone>::clone(&(&str, u8)) -> (&str, u8) 有这个实现,所以调用
let y3: (&str, u8) = (&y1).clone();

你还可以阅读和理解一下 这个帖子/例子,回答者 @vague 是我。

作者 wangshankun 2022-11-21 11:40

感谢大佬解惑,觉得距离真相更近一步了!

对照clone函数的定义,我还有一个疑惑; let t1: &(&str, u8) = &("", 1); let t2: (&str, u8) = t1.clone(); //<(&str, u8) as Clone>::clone(&(&str, u8)) -> (&str, u8) let t2: (&str, u8) = t1.clone(); //为什么不是 <&(&str, u8) as Clone>::clone(&(&(&str, u8))) -> &(&str, u8)呢?

是因为(&str, u8)属于一种类型,而&(&str, u8)不是一种类型吗?

加了一个引用符号类型应该改变了吧?为什么不是&(&str, u8)去套泛型定义:<T as Clone>::clone(&T) -> T

苦瓜小仔 2022-11-18 17:12

不知道cloned函数能把&T变成T,一直以为默认cloned出来应该和原类型保持一样

你混淆了 .clone.clonedplayground

又测了下string和vec的clone,clone出来的类型和原来一样; &(&str, u8)这个类型不一样

Rust 是静态语言,每个方法调用都是明确被定义的,所有类型都是十分明确的。

Rust 的 Clone trait 定义如下:

pub trait Clone {
    fn clone(&self) -> Self;

    fn clone_from(&mut self, source: &Self) { ... }
}

分析调用方法,总是应该解糖,弄清楚每个参数的类型是什么。

对于 T: Clone,其实例 t 通过 t.clone() 得到类型为 T 的值。.clone() 方法解糖为 <T as Clone>::clone(&T) -> T

但分析 x.clone() 的前提是 自动引用和解引用的方法调用规则。弄清楚规则是每个 Rust 新手的必经之路,因为这是隐式发生的而且很重要(不然你永远不知道如何分析调用哪个方法、怎么调用方法,所以你只观察到 a 与 b 通过 .clone() 类型相同, t1 与 t2 类型不同)。

let a = String::from("hello!");
let b = a.clone(); // 自动引用 (&a).clone()

a 的类型为 String, String 实现了 Clone,而 .clone() 方法需要 &String,Rust 自动添加引用,然后调用 <String as Clone>::clone(&String) -> String,得到 b,b 的类型为 String。

let t1: &(&str, u8) = &("", 1);
let t2: (&str, u8) = t1.clone(); // 直接调用 `<(&str, u8) as Clone>::clone(&(&str, u8)) -> (&str, u8)`
作者 wangshankun 2022-11-18 16:55

我可能猜到了为什么&a.clone 和a.clone差异,因为&a本身不具有clone方法,编译器先解引用后再尝试clone,于是出现了clone后&T变T

--
👇
wangshankun: 又测了下string和vec的clone,clone出来的类型和原来一样; &(&str, u8)这个类型不一样 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6cf94591e046c43c209bdd4d5ebe03f1

作者 wangshankun 2022-11-18 16:40

又测了下string和vec的clone,clone出来的类型和原来一样; &(&str, u8)这个类型不一样 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6cf94591e046c43c209bdd4d5ebe03f1

作者 wangshankun 2022-11-18 16:14

感谢,苦瓜小仔 大佬详细解惑!

我看到了错误日志:想一想自己实现一个FromIterator<&(K, V)>是不是有点太麻烦了;

另外,不知道cloned函数能把&T变成T,一直以为默认cloned出来应该和原类型保持一样;于是找到文档真是这样子: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cloned

rust clone真不能想当然了(;

再次感谢大佬详细解惑!

作者 wangshankun 2022-11-18 16:13

是因为(&str, {integer}) 都是基础类型,默认可以copy吧?

--
👇
Grobycn: into_iter() 之后还能用是因为实现了 Copy

报错的哪一行是因为类型不匹配

Grobycn 2022-11-17 18:32

into_iter() 之后还能用是因为实现了 Copy

报错的哪一行是因为类型不匹配

苦瓜小仔 2022-11-17 18:16

如果你有认真阅读编译器给你的错误信息的话,就不会觉得奇怪:

error[E0277]: a value of type `HashMap<&str, {integer}>` cannot be built from an iterator over elements of type `&(&str, {integer})`
  --> src/main.rs:19:22
   |
19 |     let teams_map2 = teams.iter().collect();
   |                      ^^^^^^^^^^^^ ------- required by a bound introduced by this call
   |                      |
   |                      value of type `HashMap<&str, {integer}>` cannot be built from `std::iter::Iterator<Item=&(&str, {integer})>`
   |
   = help: the trait `FromIterator<&(&str, {integer})>` is not implemented for `HashMap<&str, {integer}>`
   = help: the trait `FromIterator<(K, V)>` is implemented for `HashMap<K, V, S>`
note: required by a bound in `collect`
  1. teams.iter() 的类型是 std::iter::Iterator<Item=&(&str, {integer})>
  2. HashMap<&str, {integer}> 只实现了 FromIterator<(K, V)>,而没有实现 FromIterator<&(&str, {integer})>
  3. 所以不能从产生 &(&str, {integer}) 元素的迭代器中生成 HashMap<&str, {integer}>

teams.iter().cloned() 就是把元素的类型从 &(&str, {integer}) 转化成 (&str, {integer});所以你还可以使用 teams.iter().copied().map(|&t| t).map(|t| *t)

playground

1 共 12 条评论, 1 页