具有可为空的Option字段的Rust结构

时间:2014-08-13 04:46:21

标签: rust optional

在python中,我可能有一个看起来像这样的类:

class ClientObject(object):

    def __init__(self):
        connection = None
        connected = False

    def connect(self):
        self.connection = new Connection('server')
        self.connected = True

    def disconnect(self):
        self.connection.close()
        self.connection = None
        self.connected = False

我正在尝试做类似生锈的事情。首先,我怀疑这是否是一个生锈的好模式 - 你会用这种方式实现一个连接的客户端类吗?其次,我收到的错误是我对我的实施不了解。

pub struct Client {
    seq: int,
    connected: bool,
    socket: Option<UdpSocket>
}

impl Client {

    pub fn connect(&mut self, addr: &SocketAddr) -> ClientConnectionResult {
        match self.socket {
            Some(_) => self.disconnect(),
            None => ()
        };

        self.socket = match UdpSocket::bind(*addr) {
            Ok(s) => Some(s),
            Err(e) => return Err(to_client_error(e))
        };

        self.connected = true;

        Ok(())
    }

    pub fn disconnect(&mut self) {
        match self.socket {
            None => (),
            Some(s) => drop(s)
        };

        self.socket = None;

        self.connected = false;
    }
}

在disconnect函数中,匹配会生成编译错误,因为它会尝试移动self.socket的所有权。我想要做的是将self.socket设置为None,并允许稍后在调用connect时将其重新分配给某些内容。我该怎么做?

2 个答案:

答案 0 :(得分:9)

它可能不适用于您的用例,但是当方法使用self(当前状态)并返回表示另一个状态的另一个对象时,Rust移动语义和强类型允许便宜的“状态机”。这就是TcpListener的实现方式:它有listen()方法返回TcpAcceptor,正在使用原始侦听器。这种方法具有键入的优点:当对象处于无效状态时,您无法调用无意义的方法。

在您的情况下,它可能如下所示:

use std::kinds::markers::NoCopy;

pub struct DisconnectedClient {
    seq: int,
    _no_copy: NoCopy
}

impl DisconnectedClient {
    #[inline]
    pub fn new(seq: int) -> DisconnectedClient {
        DisconnectedClient { seq: seq, _no_copy: NoCopy }
    }

    // DisconnectedClient does not implement Copy due to NoCopy marker so you need 
    // to return it back in case of error, together with that error, otherwise 
    // it is consumed and can't be used again.
    pub fn connect(self, addr: &SocketAddr) -> Result<ConnectedClient, (DisconnectedClient, IoError)> {
        match UdpSocket::bind(*addr) {
            Ok(s) => Ok(ConnectedClient { seq: self.seq, socket: s }),
            Err(e) => Err((self, e))
        }
    }
}

pub struct ConnectedClient {
    seq: int,
    socket: UdpSocket
}

impl ConnectedClient {
    #[inline]
    pub fn disconnect(self) -> DisconnectedClient {
        // self.socket will be dropped here
        DisconnectedClient::new(self.seq)
    }

    // all operations on active connection are defined here
}

首先使用DisconnectedClient方法创建DisconnectedClient::new()。然后,当您想要连接某些内容时,使用connect()方法,该方法使用DisconnectedClient并返回表示已建立连接的新对象ConnectedClient。使用此连接后,disconnect()方法会将ConnectedClient转回DisconnectedClient

这种方法可能更复杂,但它具有静态检查错误状态的优点。您不必拥有connected() - 类似的方法;如果您的变量属于ConnectedClient类型,那么您已经知道它已连接。

答案 1 :(得分:2)

如果你的Python代码self.connected等同于self.connection is not None,那么布尔值是完全浪费时间和空间;你最好省略它,或者它是否有价值作为财产:

@property
def connected(self):
    return self.connection is not None

同样在Rust中你可以单独存储布尔值,但绝对没有意义。

impl Client {
    pub fn connect(&mut self, addr: &SocketAddr) -> ClientConnectionResult {
        self.disconnect();
        self.socket = Some(try!(UdpSocket::bind(*addr)));
        Ok(())
    }

    pub fn disconnect(&mut self) {
        self.socket = None;
    }
}

就移动的self.socket部分而言,你不能像你在那里那样做,因为正在移动它。你必须把东西放在原处。这可以通过类似.take()

的方式实现

drop是不必要的;当值超出范围或以其他方式替换时,将调用析构函数,self.socket = None将导致这种情况发生。同样,因为如果disconnect()已经socketNone工作正常,则if self.socket.is_some()的调用不需要self.disconnect()检查(或等效模式匹配)在connect

如果您希望某人能够检查它是否已连接,请为其制作一个简单的方法:

    pub fn connected(&self) -> bool {
        self.socket.is_some()
    }