如果借用的结构体字段没有实现复制或克隆特征,那么重用它的最佳方法是什么?

Pal*_*gam 5 ownership rust

我正在为 TCP 服务器编写客户端

use std::net::TcpStream;
use std::io::{Read, Write, Error};

pub struct Client {
    addr: String,
    conn: Option<TcpStream>,
}

impl Client {
    pub fn connect(&mut self) {
        let res = TcpStream::connect(&self.addr);
        match res {
            Ok(c) => {
                self.conn = Some(c);
            },
            Err(_) => panic!(),
        }
    }

    pub fn version(self) -> Result<[u8; 8], Error> {
        let mut ver: [u8; 8] = [0;8];
        let command_string = b"VERSION\r\n";
        let res = self.conn.unwrap().write(&command_string[0..]);
        match res {
            Ok(_) => self.conn.unwrap().read(&mut ver[..]),
            Err(e) => panic!(e)
        };
        Ok(ver)
    }
}
Run Code Online (Sandbox Code Playgroud)

version 方法返回错误

use std::net::TcpStream;
use std::io::{Read, Write, Error};

pub struct Client {
    addr: String,
    conn: Option<TcpStream>,
}

impl Client {
    pub fn connect(&mut self) {
        let res = TcpStream::connect(&self.addr);
        match res {
            Ok(c) => {
                self.conn = Some(c);
            },
            Err(_) => panic!(),
        }
    }

    pub fn version(self) -> Result<[u8; 8], Error> {
        let mut ver: [u8; 8] = [0;8];
        let command_string = b"VERSION\r\n";
        let res = self.conn.unwrap().write(&command_string[0..]);
        match res {
            Ok(_) => self.conn.unwrap().read(&mut ver[..]),
            Err(e) => panic!(e)
        };
        Ok(ver)
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试重用参考时。我考虑过使用#[derive(Copy, Clone)],但由于 TCPStream 没有实现这些特征,我不知道该怎么做。在方法中实现读取和写入服务器的最佳方法是什么(顺便说一句,会有很多这样的方法,例如版本,它既可以读取也可以写入服务器)

我对 Rust 还很陌生,并试图理解所有权概念,所以如果这是一个愚蠢的问题,我深表歉意,但因为我想讨论各种方法(如果确实有多种方法来解决这个问题)以重用不可克隆/可复制字段我认为这个问题值得问。

tre*_*tcl 4

当我尝试重用引用时,版本方法返回错误

self.conn不是参考;它是一种拥有的价值。因为Option::unwrap按值获取(并且其类型未实现),所以在调用它Copy后无法重用它;.unwrap()这会导致不安全。

但是您可以将调用的结果存储unwrap到一个新变量中,然后使用它:

    pub fn version(self) -> io::Result<[u8; 8]> {
        let mut ver: [u8; 8] = [0;8];
        const COMMAND_STRING: &[u8] = b"VERSION\r\n";
        let mut conn = self.conn.unwrap();
        conn.write(COMMAND_STRING)?;
        conn.read(&mut ver[..])?;
        Ok(ver)
    }
Run Code Online (Sandbox Code Playgroud)

这是可能的,因为io::Write::writeio::Read::read都接受&mut self,这意味着两者都不需要conn按值接受。相反,conn将在 结束时被删除version。另请参阅如何防止值被移动?

我做了一些进一步的更改,您可以考虑建议:

  • io::Result<_>是 的别名Result<_, io::Error>,所以我更改了声明的返回类型。这是许多图书馆中的常见模式,我发现这种形式更容易快速理解。
  • conn.read()返回一个io::Result,之前被忽略。现在conn.write()conn.read()都使用特殊运算符向调用者引发错误?。如果您希望错误发生恐慌而不是传播,您可以替换?为。.unwrap()请参阅这个问号运算符是关于什么的?
  • COMMAND_STRING是一只const蚂蚁。与仅仅使用这个名字相比,拥有这个名字是否有用是有争议的conn.write(b"VERSION\r\n"),但如果你想要拥有它,aconst似乎是正确的选择。原则上,编译器可以const比常规变量更好地优化 s;实际上,在这种情况下不太可能产生太大的差异,但是当 aconst更明确地说明如何使用该值时,没有理由不这样做。

使用实际参考

如果您实际上想通过引用version进行引用self,因此您希望它解开并销毁self.conn,您可以使用Option::as_ref来使其工作。

    pub fn version(&self) -> io::Result<[u8; 8]> {  // NOTE: `&self`
        let mut ver: [u8; 8] = [0;8];
        let mut conn = self.conn.as_ref().unwrap(); // NOTE: `.as_ref()`
        conn.write(b"VERSION\r\n")?;
        conn.read(&mut ver[..])?;
        Ok(ver)
    }
Run Code Online (Sandbox Code Playgroud)

游乐场链接

as_ref将 a 转换&Option<_>为 a Option<&_>,以便unwrapping 它不会消耗self.conn而只是返回引用。请参阅展开时无法移出借用的内容