根据编译目标 OS,Rust 将不同类型的值分配给变量的惯用方式是什么?

What is the idiomatic way in Rust of assigning values of different types to a variable, depending on the compilation target OS?

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

在 Mac 上进行本地开发时,AF_VSOCK API 不可用,因为没有 hypervisor -> VM 连接 — 它只是 运行 本机使用 cargo run.

在本地 运行ning 时,我一直在创建标准 tokio::net::TcpListener struct and in production I have been creating a tokio_vsock::VsockListener。这两个结构 大部分 可以互换,并公开相同的方法。无论使用哪个结构,其余代码都能完美运行。

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

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

    // Local TCP listener (comment out in production)
    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");

    // This works regardless of which listener is used
    let mut incoming = listener.incoming();

    while let Some(socket) = incoming.next().await {
        match socket {
            Ok(mut stream) => {
                // Do something
            }
        }
    }

    Ok(())
}

我尝试使用 cfg!() 宏并将 target_os 设置为条件,但编译器抱怨两个 bind() 方法返回的类型不匹配。

我的问题是:在 Rust 中用不同类型[=]分配不同值的惯用方式是什么 到变量,取决于编译目标 OS?

有多种选择。关于 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();
}

(参见 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");
...
}

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

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