Rust: 尝试从Stdin获取下一个字节时的所有权问题

huangapple go评论78阅读模式
英文:

Rust: Ownership problem when trying to get next byte from Stdin

问题

  1. The reason for the error in your code is that the bytes() method takes ownership of the underlying reader, and you are trying to call it on a mutable reference to the reader (self.0). This results in a move error because the R type (the reader) does not implement the Copy trait, and calling bytes() consumes the reader. To fix this, you can use the by_ref() method to create a reference that can be consumed by bytes() without moving the original reader. Here's how you can modify your code to fix this issue:

    Replace this line:

    let buf = self.0.by_ref().bytes()
    

    with:

    let buf = self.0.by_ref().bytes()
    

    By using by_ref(), you create a reference to the reader, and the bytes() method can work with it without taking ownership.

  2. To achieve a getchar-like functionality in Rust, your approach is reasonable. The char() method you added to the IO struct allows you to read a single character from stdin. However, the issue in your code needs to be fixed as explained in the previous answer.

  3. The difference you observed in the return type of self.0.by_ref().bytes() between the original read function and your code might be due to changes in the Rust standard library or how the bytes() method is implemented in the specific version of Rust you are using. The Bytes type returned by bytes() represents a stream of bytes from the reader. In the original code, the reference types may have been handled differently or had different traits associated with them, resulting in the type difference. This is not unusual, and such differences can occur between different versions of libraries or code.

英文:

I was modifing a code snippet from github which enable fast i/o in Rust for competitive-programming.

I want to add a feature making it can read single character from stdin, just like getchar in C. My approach was a combination of the pub fn read<T: std::str::FromStr> in the original code snippet and this SO answer

//////////////////////////////////////////////////////////////////////
/// https://github.com/kenkoooo/competitive-programming-rs/blob/master/src/utils/scanner.rs
/// let (stdin, stdout) = (std::io::stdin(), std::io::stdout());
/// let mut sc = IO::new(stdin.lock(), stdout.lock());
pub struct IO<R, W: std::io::Write>(R, std::io::BufWriter<W>);

impl<R: std::io::Read, W: std::io::Write> IO<R, W> {
    pub fn new(r: R, w: W) -> IO<R, W> {
        IO(r, std::io::BufWriter::new(w))
    }
    pub fn write<S: ToString>(&mut self, s: S) {
        use std::io::Write;
        self.1.write_all(s.to_string().as_bytes()).unwrap();
    }
    pub fn read<T: std::str::FromStr>(&mut self) -> T {
        use std::io::Read;
        let buf = self
            .0
            .by_ref()
            .bytes()
            .map(|b| b.unwrap())
            .skip_while(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
            .take_while(|&b| b != b' ' && b != b'\n' && b != b'\r' && b != b'\t')
            .collect::<Vec<_>>();
        unsafe { std::str::from_utf8_unchecked(&buf) }
            .parse()
            .ok()
            .expect("Parse error.")
    }
    pub fn usize0(&mut self) -> usize {
        self.read::<usize>() - 1
    }
    pub fn vec<T: std::str::FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read()).collect()
    }
    pub fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
    pub fn char(&mut self) -> char {
        self
        .0
        .by_ref()
        .bytes()
        .next()
        .unwrap()
        .unwrap() as char
    }
}
///////////////////////////////////////////////////////////////////////

#[allow(non_snake_case)]

fn main() {
    let (stdin, stdout) = (std::io::stdin(), std::io::stdout());
    let mut sc = IO::new(stdin.lock(), stdout.lock());

    let c = sc.char();
    sc.write(c);
}

cargo run and the output was:

$ cargo run
warning: unused manifest key: package.author
   Compiling ralgo v0.1.0 (/home/xxx/ralgo)
error[E0507]: cannot move out of a mutable reference
   --> src/main.rs:40:9
    |
40  | /         self
41  | |         .0
42  | |         .by_ref()
43  | |         .bytes()
    | |          ------^
    | |__________|_____|
    |            |     move occurs because value has type `R`, which does not implement the `Copy` trait
    |            value moved due to this method call
    |
note: `bytes` takes ownership of the receiver `self`, which moves value
   --> /home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:922:14
    |
922 |     fn bytes(self) -> Bytes<Self>
    |              ^^^^

For more information about this error, try `rustc --explain E0507`.
error: could not compile `ralgo` due to previous error

My questions:

  1. The reason for this error and how to fix it.
  2. Is there any better way to achieve getchar in Rust?
  3. Why the rust-analyser shows the return type of let buf = self.0.by_ref().bytes() in the original read function is Bytes<&mut R> but for my code self.0.by_ref().bytes() it was Bytes<R>?

答案1

得分: 2

Upfront, I want to note that your read is unsound. And furthermore the cost of allocating a vector is almost certainly going to dwarf the cost of validating that said vector is UTF8, so it's rather unnecessary.

The reason for this error and how to fix it.

Because you don't use std::io::Read globally, the compiler knows that R specifically implements Read (as that's a bound), it does not know about any other implementation of Read. Notably, it does not know about impl<R: Read + ?Sized> Read for &mut R. Thus as far as it is concerned, the closest thing which would have a bytes method is the original reader, which it tries to deref. The error is much clearer if you assign the result of by_ref to a local variable.

Just stop using fully qualified path like this, especially for traits. Your code is strictly less readable than if you just imported Read, Write, and BufWriter at the top.

Is there any better way to achieve getchar in Rust?

getchar is a horrible function so I'm not too clear on why you'd want that. But I would suggest just using byteorder's read_u8.

Why the rust-analyser shows the return type

Possibly because it's getting confused and assumes all the traits are in scope.

.skip_while(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
.take_while(|&b| b != b' ' && b != b'\n' && b != b'\r' && b != b'\t')

These seem like complicated ways of not calling u8::is_ascii_whitespace.

英文:

Upfront, I want to note that your read is unsound. And furthermore the cost of allocating a vector is almost certainly going to dwarf the cost of validating that said vector is UTF8, so it's rather unnecessary.

> The reason for this error and how to fix it.

Because you don't use std::io::Read globally, the compiler knows that R specifically implements Read (as that's a bound), it does not know about any other implementation of Read. Notably, it does not know about impl&lt;R: Read + ?Sized&gt; Read for &amp;mut R. Thus as far as it is concerned, the closest thing which would have a bytes method is the original reader, which it tries to deref'. The error is much clearer if you assign the result of by_ref to a local variable.

Just stop using fully qualified path like this, especially for traits. Your code is strictly less readable than if you just imported Read, Write, and BufWriter at the top.

> Is there any better way to achieve getchar in Rust?

getchar is a horrible function so I'm not too clear on why you'd want that. But I would suggest just using byteorder's read_u8.

> Why the rust-analyser shows the return type

Possibly because it's getting confused and assumes all the traits are in scope.

> .skip_while(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
> .take_while(|&b| b != b' ' && b != b'\n' && b != b'\r' && b != b'\t')

These seem like complicated ways of not calling u8::is_ascii_whitespace.

huangapple
  • 本文由 发表于 2023年2月8日 21:38:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75386618.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定