Rust 中根据编译目标操作系统将不同类型的值分配给变量的惯用方法是什么?

nar*_*ruc 5 compiler-errors compilation rust rust-cargo rust-tokio

我正在开发一个绑定到 Tokio 套接字并管理 TCP 连接的代码库。在生产中,它绑定到AF_VSOCK使用tokio-vsock板条箱。

\n

在 Mac 上本地开发时,AF_VSOCKAPI 不可用,因为没有hypervisor -> VM连接 \xe2\x80\x94\xc2\xa0it\ 只是使用cargo run.

\n

在本地运行时,我一直在创建一个标准tokio::net::TcpListener结构,在生产中我一直在创建一个tokio_vsock::VsockListener. 这两种结构大多可以互换并公开相同的方法。无论使用哪个结构,其余代码都可以完美运行。

\n

到目前为止,我只是保留了这两个结构,并简单地注释掉了本地不需要的结构 \xe2\x80\x94 这显然不是“好的做法”。我的代码如下:

\n
#[tokio::main]\nasync fn main() -> Result<(), ()> {\n    // Production AF_VSOCK listener (comment out locally)\n    let mut listener = tokio_vsock::VsockListener::bind(\n        &SockAddr::Vsock(\n          VsockAddr::new(\n            VMADDR_CID_ANY,\n            LISTEN_PORT,\n          )\n        )\n    )\n    .expect("Unable to bind AF_VSOCK listener");\n\n    // Local TCP listener (comment out in production)\n    let mut listener = tokio::net::TcpListener::bind(\n        std::net::SocketAddr::new(\n            std::net::IpAddr::V4(\n                std::net::Ipv4Addr::new(0, 0, 0, 0)\n            ),\n            LISTEN_PORT as u16,\n        )\n    )\n    .await\n    .expect("Unable to bind TCP listener");\n\n    // This works regardless of which listener is used\n    let mut incoming = listener.incoming();\n\n    while let Some(socket) = incoming.next().await {\n        match socket {\n            Ok(mut stream) => {\n                // Do something\n            }\n        }\n    }\n\n    Ok(())\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我尝试使用cfg!()带有target_osset 的宏作为条件,但编译器抱怨这两种bind()方法返回的类型不匹配。

\n

我的问题是: Rust 中根据编译目标操作系统为变量分配不同类型不同值的惯用方法是什么?

\n

som*_*ium 2

有多种选择。关于 stdlib 本身的用法,最简单也是最常见的一种是使用宏#[cfg](而不是 )cfg!()。以下代码片段阐明了它的用法:

struct Linux;
impl Linux {
    fn x(&self) -> Linux {        
        println!("Linux");
        Linux
    }
}

struct Windows;
impl Windows {
    fn x(&self) -> Windows {
        println!("Windows");
        Windows
    }
}

fn main() {
    #[cfg(not(target_os = "linux"))]
    let obj = {
        let obj = Linux;
        obj
    };
    #[cfg(not(target_os = "windows"))]
    let obj = {
        let obj = Windows;
        obj
    };
    let _ = obj.x();
}
Run Code Online (Sandbox Code Playgroud)

(参见https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7088980d24c4a960c2158b091899d24d)。

在你的情况下,这将是(未经测试):

#[tokio::main]
async fn main() -> Result<(), ()> {
    #[cfg(target_os = "linux")]
    let mut listener = tokio_vsock::VsockListener::bind(
        &SockAddr::Vsock(
          VsockAddr::new(
            VMADDR_CID_ANY,
            LISTEN_PORT,
          )
        )
    )
    .expect("Unable to bind AF_VSOCK listener");

    #[cfg(target_os = "Mac")]
    let mut listener = tokio::net::TcpListener::bind(
        std::net::SocketAddr::new(
            std::net::IpAddr::V4(
                std::net::Ipv4Addr::new(0, 0, 0, 0)
            ),
            LISTEN_PORT as u16,
        )
    )
    .await
    .expect("Unable to bind TCP listener");
...
}
Run Code Online (Sandbox Code Playgroud)

检查https://doc.rust-lang.org/reference/conditional-compilation.html了解可用条件,包括功能标志,以防 target_os 不够适用。

#[cfg]和之间的主要区别cfg!()cfg!不删除代码。根据它的文档:“cfg!与 #[cfg] 不同,不会删除任何代码,仅评估 true 或 false”。由于使用#[cfg]更类似于 C/C++ 中的 if-def 并删除未使用的代码时会出现编译错误,因此编译器永远不会看到类型不匹配。