https://doc.rust-lang.org/src/std/fs.rs.html#614
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
https://doc.rust-lang.org/src/std/fs.rs.html#660
impl Read for &File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
疑问:先不关注两者实现相同,这两种实现时是什么关系?我理解file.read()都应该调用到第一个吧?
1
共 19 条评论, 1 页
评论区
写评论嗯,这是一个原因。
--
👇
NaokiLH: 所以原因是File的特殊性是吗
我也很存疑,一般有个
impl<R: Read + ?Sized> Read for &mut R
自然&mut File就能用了,而且也比较符合这个Trait的设计规格,体现出IO是有副作用需要mut的这一概念,对于Write也是,但是实际上为&File实现Read并没有什么问题,因为file的设计写入是通过的libc进行的,不需要file本身是mut,自然签名&mut &File也就没什么问题,但是总是感觉哪里不对劲。所以原因是File的特殊性是吗
--
👇
yuikonnu: 国庆快乐!我再补充一下吧。
对于
impl<'a> Read for &'a File
,Read::read
的签名应当是fn read<'b>(self: &'b mut &'a File, ...) -> ...
。这是非常自然的,对T
(这里是&'a File
)实现Read
,那Read::read
的参数&mut self
就是&mut T
,没有任何特殊性。看起来拿到一个
&mut &T
没有什么意义,因为解引用也只能得到一个&T
,是不可变的。但这里特殊的东西是File
,它背后是操作系统的文件,可以看做其具有内部可变性且线程安全,拿到&File
就能够执行“读取”操作而不用担心其内部的状态因共享被破坏。但标准库为读取设计的Trait
Read
中的一些方法默认了实现者需要拿到独占借用&mut T
,可我们知道对于File
只需要&File
就够了,所以标准库在这样特殊的情况下,为&File
实现了Read
(以及其它文件操作的Traits)。最终我们看到了一个奇怪的&mut &File
,它的前半部分&mut
源自Read::read
(以及其它函数)的签名,后半部分出现了&File
(而不是预期的&mut File
)是因为操作系统为File
解决了原本Rust区分&mut T
与&T
想解决的问题。即使标准库不为
&File
实现Read
,我们也可以用Arc<Mutex<File>>
达到相同的目的,但这损失了效率。国庆快乐!我再补充一下吧。
对于
impl<'a> Read for &'a File
,Read::read
的签名应当是fn read<'b>(self: &'b mut &'a File, ...) -> ...
。这是非常自然的,对T
(这里是&'a File
)实现Read
,那Read::read
的参数&mut self
就是&mut T
,没有任何特殊性。看起来拿到一个
&mut &T
没有什么意义,因为解引用也只能得到一个&T
,是不可变的。但这里特殊的东西是File
,它背后是操作系统的文件,可以看做其具有内部可变性且线程安全,拿到&File
就能够执行“读取”操作而不用担心其内部的状态因共享被破坏。但标准库为读取设计的Trait
Read
中的一些方法默认了实现者需要拿到独占借用&mut T
,可我们知道对于File
只需要&File
就够了,所以标准库在这样特殊的情况下,为&File
实现了Read
(以及其它文件操作的Traits)。最终我们看到了一个奇怪的&mut &File
,它的前半部分&mut
源自Read::read
(以及其它函数)的签名,后半部分出现了&File
(而不是预期的&mut File
)是因为操作系统为File
解决了原本Rust区分&mut T
与&T
想解决的问题。即使标准库不为
&File
实现Read
,我们也可以用Arc<Mutex<File>>
达到相同的目的,但这损失了效率。谢谢!我先贴下您的说明:
然后说下我的理解:
怎么说呢,我觉得既然能编译过去,就肯定有办法去解释。但是rust的这个逻辑不是很简洁,或者过于复杂,很难让人直观的理解。
按照直观逻辑,能进入到
fn read(&mut self, ...)
,就应该已经获取到了对Foo的所谓独占引用,而不是在调用self.a = 10;
时才触发编译错误。我比较认同 @yuikonnu 的解释:
我可以认为:在这种情况下,
fn read(&mut self, ...)
的&mut self其实就是为了满足read的签名,是个“假的”。--
👇
苦瓜小仔: 对于 &mut &self 的回答:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a5b5f90902cc82f53494e812324bdc36
--
👇
chuqingq: 谢谢解答!
我没有说清楚,我的意思是
trait Read
的read
方法对receiver的要求是&mut
。对于问题2,我还是觉得别扭。
我认为
trait Read
的fn read
的&mut self
应该就是表达这个意思:需要一个可变引用作为receiver,在read
方法中可以修改。但是下面这个例子如果注掉
self.a = 10;
这行,可以编译通过,去注释就不能编译通过。这是不是说明read的&mut self
其实是个“假的”?MyStruct在read中修改自身,理所当然的要看read的receiver是&self还是&mut self,但竟然还要看impl Read for的是&MyStruct还是&mut MyStruct,而且竟竟然还和read的实现有关,read中要真的改了self,又编译不过。这个怎么说也有点惊奇啊
--
👇
苦瓜小仔: > trait Read 本身要求 receiver 是 &mut
这句话错了。trait 并不会要求其方法内的任何参数是哪种引用/所有权形式,所以你要看具体的方法签名。
Read / Write 的大部分方法签名的确使用了 &mut self ,但是也有使用 &self 的啊。所以为什么没有理由不通过呢?反过来说,如果因为有 &mut self 的方法就拒绝编译这个 trait impl,那么这意味着不允许含 &self 的方法在 shared reference 时使用,你觉得对吗?
任何事物都有两面性,有多强大就有多复杂。这的确很复杂,但是这完全不是重点。 重点是,你学习/理解了一种写法/用法,并且能在将来的场景中模仿/应用实现 (implmentation)。
--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?--
👇
yuikonnu: 1. 这不是一个Common的情况,许多Trait都有类似
impl<'_, T: Trait> Trait for &'_ T
的实现; 2. 文件的状态由操作系统维护,不是File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
对于 &mut &self 的回答:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a5b5f90902cc82f53494e812324bdc36
--
👇
chuqingq: 谢谢解答!
我没有说清楚,我的意思是
trait Read
的read
方法对receiver的要求是&mut
。对于问题2,我还是觉得别扭。
我认为
trait Read
的fn read
的&mut self
应该就是表达这个意思:需要一个可变引用作为receiver,在read
方法中可以修改。但是下面这个例子如果注掉
self.a = 10;
这行,可以编译通过,去注释就不能编译通过。这是不是说明read的&mut self
其实是个“假的”?MyStruct在read中修改自身,理所当然的要看read的receiver是&self还是&mut self,但竟然还要看impl Read for的是&MyStruct还是&mut MyStruct,而且竟竟然还和read的实现有关,read中要真的改了self,又编译不过。这个怎么说也有点惊奇啊
--
👇
苦瓜小仔: > trait Read 本身要求 receiver 是 &mut
这句话错了。trait 并不会要求其方法内的任何参数是哪种引用/所有权形式,所以你要看具体的方法签名。
Read / Write 的大部分方法签名的确使用了 &mut self ,但是也有使用 &self 的啊。所以为什么没有理由不通过呢?反过来说,如果因为有 &mut self 的方法就拒绝编译这个 trait impl,那么这意味着不允许含 &self 的方法在 shared reference 时使用,你觉得对吗?
任何事物都有两面性,有多强大就有多复杂。这的确很复杂,但是这完全不是重点。 重点是,你学习/理解了一种写法/用法,并且能在将来的场景中模仿/应用实现 (implmentation)。
--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?--
👇
yuikonnu: 1. 这不是一个Common的情况,许多Trait都有类似
impl<'_, T: Trait> Trait for &'_ T
的实现; 2. 文件的状态由操作系统维护,不是File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
明白了,谢谢解答!
--
👇
yuikonnu: 对于1:
如果这个
MyStruct
是你自己实现的,而不是对某种资源的封装,那么实现Read
势必需要改变自身内部的状态。那么impl<'_> Read for &'_ MyStruct
是没有意义的。而impl Read for &mut MyStruct
不需要你实现,Read
的设计者已经为你实现了。「这不是一个Common的情况」包含两点,一是
Read
在多数情况下伴随状态的改变,所以在这个Trait的实现中不能为&T
添加一个泛型的实现;二是File
刚好作为特殊的一份子,它是对操作系统资源的封装,而操作系统早已考虑到多线程读写的情况,它像AtomicI32
这样的原子类型一样可以随意“可变地”共享。多数情况下这两个条件不会同时满足。对于2:
我没有太懂你的意思,在
impl<'a> Read for &'a MyStruct
的环境下,read
接收一个&mut &MyStruct
。这里的&mut &
应当只是为了满足Read::read
的签名。--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?谢谢解答!
我没有说清楚,我的意思是
trait Read
的read
方法对receiver的要求是&mut
。对于问题2,我还是觉得别扭。
我认为
trait Read
的fn read
的&mut self
应该就是表达这个意思:需要一个可变引用作为receiver,在read
方法中可以修改。但是下面这个例子如果注掉
self.a = 10;
这行,可以编译通过,去注释就不能编译通过。这是不是说明read的&mut self
其实是个“假的”?MyStruct在read中修改自身,理所当然的要看read的receiver是&self还是&mut self,但竟然还要看impl Read for的是&MyStruct还是&mut MyStruct,而且竟竟然还和read的实现有关,read中要真的改了self,又编译不过。这个怎么说也有点惊奇啊
--
👇
苦瓜小仔: > trait Read 本身要求 receiver 是 &mut
这句话错了。trait 并不会要求其方法内的任何参数是哪种引用/所有权形式,所以你要看具体的方法签名。
Read / Write 的大部分方法签名的确使用了 &mut self ,但是也有使用 &self 的啊。所以为什么没有理由不通过呢?反过来说,如果因为有 &mut self 的方法就拒绝编译这个 trait impl,那么这意味着不允许含 &self 的方法在 shared reference 时使用,你觉得对吗?
任何事物都有两面性,有多强大就有多复杂。这的确很复杂,但是这完全不是重点。 重点是,你学习/理解了一种写法/用法,并且能在将来的场景中模仿/应用实现 (implmentation)。
--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?--
👇
yuikonnu: 1. 这不是一个Common的情况,许多Trait都有类似
impl<'_, T: Trait> Trait for &'_ T
的实现; 2. 文件的状态由操作系统维护,不是File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
对于1:
如果这个
MyStruct
是你自己实现的,而不是对某种资源的封装,那么实现Read
势必需要改变自身内部的状态。那么impl<'_> Read for &'_ MyStruct
是没有意义的。而impl Read for &mut MyStruct
不需要你实现,Read
的设计者已经为你实现了。「这不是一个Common的情况」包含两点,一是
Read
在多数情况下伴随状态的改变,所以在这个Trait的实现中不能为&T
添加一个泛型的实现;二是File
刚好作为特殊的一份子,它是对操作系统资源的封装,而操作系统早已考虑到多线程读写的情况,它像AtomicI32
这样的原子类型一样可以随意“可变地”共享。多数情况下这两个条件不会同时满足。对于2:
我没有太懂你的意思,在
impl<'a> Read for &'a MyStruct
的环境下,read
接收一个&mut &MyStruct
。这里的&mut &
应当只是为了满足Read::read
的签名。--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?这句话错了。trait 并不会要求其方法内的任何参数是哪种引用/所有权形式,所以你要看具体的方法签名。
Read / Write 的大部分方法签名的确使用了 &mut self ,但是也有使用 &self 的啊。所以为什么没有理由不通过呢?反过来说,如果因为有 &mut self 的方法就拒绝编译这个 trait impl,那么这意味着不允许含 &self 的方法在 shared reference 时使用,你觉得对吗?
任何事物都有两面性,有多强大就有多复杂。这的确很复杂,但是这完全不是重点。 重点是,你学习/理解了一种写法/用法,并且能在将来的场景中模仿/应用实现 (implmentation)。
--
👇
chuqingq: 谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?--
👇
yuikonnu: 1. 这不是一个Common的情况,许多Trait都有类似
impl<'_, T: Trait> Trait for &'_ T
的实现; 2. 文件的状态由操作系统维护,不是File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
谢谢! 其实我关注的不是
std::fs::File
,当成是某个自定义struct吧,MyStruct
。 对于我前面的问题1,我纠结的是,trait Read
本身要求receiver是&mut
,且对于MyStruct
来说,impl Read for MyStruct
还不够,还需要impl Read for &MyStruct
,甚至还需要impl Read for &mut MyStruct
,这样是不是有点太复杂了?对于问题2,因为
trait Read
本身要求receiver是&mut
,如果只是impl Read for &MyStruct
,而不是impl Read for &mut MyStruct
,是不是应该编译不通过?--
👇
yuikonnu: 1. 这不是一个Common的情况,许多Trait都有类似
impl<'_, T: Trait> Trait for &'_ T
的实现; 2. 文件的状态由操作系统维护,不是File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
impl<'_, T: Trait> Trait for &'_ T
的实现;File
。可以看看这个问题(及其duplicate)的回答:https://stackoverflow.com/questions/60140539/why-is-the-write-trait-implemented-for-file-and-tcpstream-is-it-safe
--
👇
chuqingq: 这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
这里又引申出来两个问题:
impl Trait for &Type
确实可以为调用方解决bar(&f)
传递引用,而无需move的问题,但是实现方每次都要搞两套实现,违背了DRY原则这里传递的
&f
,从语义上来说应该是不可变引用,但是却可以通过f.read()
实际改变自己请问是不是存在这样的问题?
明白了,谢谢谢谢!
--
👇
Bai-Jinlin: 就是假如你这个函数参数是impl Read的话,而且File没有实现impl Read for &File你就只能传所有权进去
--
👇
chuqingq: 谢谢!没看明白,有没有具体的例子?或者官方文档哪里有讲,帮给个链接?
--
👇
Bai-Jinlin: 范型会用上,作为范型参数不会自动引用,没有第二个的话你就只能传File了
明白了,非常感谢!
--
👇
Grobycn: ``` use std::io::Read;
#[derive(Debug)] struct Foo;
impl Read for Foo { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { Ok(0) } }
fn bar<T: Read>(r: T) {}
fn main() { let f = Foo; // bar(&f); the trait
std::io::Read
is not implemented for&Foo
bar(f); // println!("{:?}", f); borrow of moved value:f
}就是假如你这个函数参数是impl Read的话,而且File没有实现impl Read for &File你就只能传所有权进去
--
👇
chuqingq: 谢谢!没看明白,有没有具体的例子?或者官方文档哪里有讲,帮给个链接?
--
👇
Bai-Jinlin: 范型会用上,作为范型参数不会自动引用,没有第二个的话你就只能传File了
--
👇
chuqingq: 谢谢!没看明白,有没有具体的例子?或者官方文档哪里有讲,帮给个链接?
--
👇
Bai-Jinlin: 范型会用上,作为范型参数不会自动引用,没有第二个的话你就只能传File了
谢谢!没看明白,有没有具体的例子?或者官方文档哪里有讲,帮给个链接?
--
👇
Bai-Jinlin: 范型会用上,作为范型参数不会自动引用,没有第二个的话你就只能传File了
范型会用上,作为范型参数不会自动引用,没有第二个的话你就只能传File了