Skip to content

binary operation != cannot be applied to type tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>> #15180

Open
@RazeLighter777

Description

@RazeLighter777

Summary

After running the cargo clippy command it told me there was a bug in the tool and that I should file an issue. It's complaining about this error:

error[E0369]: binary operation `!=` cannot be applied to type `tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>`
   --> vxlan-sidecar/src/main.rs:152:33

See attached for the log output and file that produces this bug.

[nix-shell:~/Code/carve]$ cargo clippy --fix --all-targets
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2024 which implies `resolver = "3"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2024 resolver, specify `workspace.resolver = "3"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2024 which implies `resolver = "3"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2024 resolver, specify `workspace.resolver = "3"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
    Checking carve v0.1.0 (/home/justin/Code/carve/carve)
       Fixed carve/src/redis_manager.rs (2 fixes)
    Checking vtep v0.1.0 (/home/justin/Code/carve/vtep)
    Checking vxlan-sidecar v0.1.0 (/home/justin/Code/carve/vxlan-sidecar)
    Checking canary v0.1.0 (/home/justin/Code/carve/canary)
    Checking qemu-box v0.1.0 (/home/justin/Code/carve/qemu-box)
    Checking carve-api v0.1.0 (/home/justin/Code/carve/carve-api)
    Checking carve-novnc-nginx v0.1.0 (/home/justin/Code/carve/carve-novnc-nginx)
warning: failed to automatically apply fixes suggested by rustc to crate `vxlan_sidecar`

after fixes were automatically applied the compiler reported errors within these files:

  * vxlan-sidecar/src/main.rs

This likely indicates a bug in either rustc or cargo itself,
and we would appreciate a bug report! You're likely to see
a number of compiler warnings after this message which cargo
attempted to fix but failed. If you could open an issue at
https://github.com/rust-lang/rust-clippy/issues
quoting the full output of this command we'd be very appreciative!
Note that you may be able to make some more progress in the near-term
fixing code with the `--broken-code` flag

The following errors were reported:
error[E0369]: binary operation `!=` cannot be applied to type `tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>`
   --> vxlan-sidecar/src/main.rs:152:33
    |
152 |                         if last != Some(ip) {
    |                            ---- ^^ -------- std::option::Option<std::net::IpAddr>
    |                            |
    |                            tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>
    |
note: the foreign item type `tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>` doesn't implement `std::cmp::PartialEq<std::option::Option<std::net::IpAddr>>`
   --> /home/justin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.1/src/sync/mutex.rs:151:1
    |
151 | pub struct MutexGuard<'a, T: ?Sized> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not implement `std::cmp::PartialEq<std::option::Option<std::net::IpAddr>>`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0369`.
Original diagnostics will follow.

warning: function call inside of `expect`
  --> vxlan-sidecar/src/main.rs:90:10
   |
90 |           .expect(
   |  __________^
91 | |             format!(
92 | |                 "Competition {} not found in config",
93 | |                 competition_name.as_str()
94 | |             )
95 | |             .as_str(),
96 | |         );
   | |_________^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
   = note: `#[warn(clippy::expect_fun_call)]` on by default
help: try
   |
90 ~         .unwrap_or_else(|| panic!("Competition {} not found in config",
91 ~                 competition_name.as_str()));
   |

warning: useless use of `format!`
   --> vxlan-sidecar/src/main.rs:126:14
    |
126 |             &format!("{}", competition.cidr.as_ref().unwrap()),
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `competition.cidr.as_ref().unwrap().to_string()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
    = note: `#[warn(clippy::useless_format)]` on by default

warning: using `print!()` with a format string that ends in a single newline
   --> vxlan-sidecar/src/main.rs:147:5
    |
147 | /     print!(
148 | |         "Starting DNS resolution loop for VTEP host: {}\n",
149 | |         vtep_host
150 | |     );
    | |_____^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline
    = note: `#[warn(clippy::print_with_newline)]` on by default
help: use `println!` instead
    |
147 ~     println!(
148 ~         "Starting DNS resolution loop for VTEP host: {}",
    |

warning: this `map_or` can be simplified
   --> vxlan-sidecar/src/main.rs:157:28
    |
157 |                         if last.map_or(true, |prev| prev != ip) {
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_map_or
    = note: `#[warn(clippy::unnecessary_map_or)]` on by default
help: use a standard comparison instead
    |
157 -                         if last.map_or(true, |prev| prev != ip) {
157 +                         if last != Some(ip) {
    |

warning: `vxlan-sidecar` (bin "vxlan-sidecar" test) generated 4 warnings (run `cargo clippy --fix --bin "vxlan-sidecar" --tests` to apply 4 suggestions)
warning: redundant pattern matching, consider using `is_ok()`
  --> qemu-box/src/main.rs:21:16
   |
21 |         if let Ok(_) = Command::new("kill").arg("-0").arg(pid.trim()).output() {
   |         -------^^^^^---------------------------------------------------------- help: try: `if Command::new("kill").arg("-0").arg(pid.trim()).output().is_ok()`
   |
   = note: this will change drop order of the result, as well as all temporaries
   = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
   = note: `#[warn(clippy::redundant_pattern_matching)]` on by default

warning: `qemu-box` (bin "qemu-box" test) generated 1 warning
warning: `qemu-box` (bin "qemu-box") generated 1 warning (1 duplicate)
warning: failed to automatically apply fixes suggested by rustc to crate `vxlan_sidecar`

after fixes were automatically applied the compiler reported errors within these files:

  * vxlan-sidecar/src/main.rs

This likely indicates a bug in either rustc or cargo itself,
and we would appreciate a bug report! You're likely to see
a number of compiler warnings after this message which cargo
attempted to fix but failed. If you could open an issue at
https://github.com/rust-lang/rust-clippy/issues
quoting the full output of this command we'd be very appreciative!
Note that you may be able to make some more progress in the near-term
fixing code with the `--broken-code` flag

The following errors were reported:
error[E0369]: binary operation `!=` cannot be applied to type `tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>`
   --> vxlan-sidecar/src/main.rs:152:33
    |
152 |                         if last != Some(ip) {
    |                            ---- ^^ -------- std::option::Option<std::net::IpAddr>
    |                            |
    |                            tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>
    |
note: the foreign item type `tokio::sync::MutexGuard<'_, std::option::Option<std::net::IpAddr>>` doesn't implement `std::cmp::PartialEq<std::option::Option<std::net::IpAddr>>`
   --> /home/justin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.45.1/src/sync/mutex.rs:151:1
    |
151 | pub struct MutexGuard<'a, T: ?Sized> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not implement `std::cmp::PartialEq<std::option::Option<std::net::IpAddr>>`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0369`.
Original diagnostics will follow.

warning: `vxlan-sidecar` (bin "vxlan-sidecar") generated 4 warnings (4 duplicates)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.17s

Reproducer

Following file produced the bug:

use actix_web::{App, HttpServer, Responder, get, web};
use carve::config::AppConfig;
use std::env;
use std::net::IpAddr;
use std::process::Command;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{Duration, sleep};

#[derive(Clone)]
enum HealthStatus {
    Healthy,
    Unhealthy,
}

type SharedHealth = Arc<Mutex<HealthStatus>>;

#[get("/api/health")]
async fn health(health: web::Data<SharedHealth>) -> impl Responder {
    let status = health.lock().await;
    match *status {
        HealthStatus::Healthy => "Healthy",
        HealthStatus::Unhealthy => "Unhealthy",
    }
}

fn create_vxlan_interface(vxlan_id: &str) -> Result<(), String> {
    // Remove vxlan0 if it exists
    let _ = Command::new("ip").args(["link", "del", "vxlan0"]).status();
    // Create vxlan0 with remote
    let status = Command::new("ip")
        .args([
            "link", "add", "vxlan0", "type", "vxlan", "id", vxlan_id, "dev", "eth0", "dstport",
            "4789",
        ])
        .status()
        .map_err(|e| format!("Failed to create vxlan0: {}", e))?;
    if !status.success() {
        return Err("Failed to create vxlan0 interface".into());
    }
    // Bring up vxlan0
    Command::new("ip")
        .args(["link", "set", "vxlan0", "up"])
        .status()
        .map_err(|e| format!("Failed to bring up vxlan0: {}", e))?;
    Ok(())
}

fn create_bridge(cidr: &str) -> Result<(), String> {
    // Remove br0 if it exists
    let _ = Command::new("ip").args(["link", "del", "br0"]).status();
    // Create br0
    let status = Command::new("ip")
        .args(["link", "add", "name", "br0", "type", "bridge"])
        .status()
        .map_err(|e| format!("Failed to create br0: {}", e))?;
    if !status.success() {
        return Err("Failed to create br0 interface".into());
    }
    // Bring up br0
    Command::new("ip")
        .args(["link", "set", "br0", "up"])
        .status()
        .map_err(|e| format!("Failed to bring up br0: {}", e))?;
    // Add vxlan0 to br0
    Command::new("ip")
        .args(["link", "set", "vxlan0", "master", "br0"])
        .status()
        .map_err(|e| format!("Failed to add vxlan0 to br0: {}", e))?;
    // Add IP address to br0
    // Set vxlan0 address to .254 of the given CIDR
    let ip_address = format!("{}/24", cidr.trim_end_matches(".0").to_string() + ".254");
    // println!("Setting br0 address to {}", ip_address);
    Command::new("ip")
        .args(["addr", "add", &ip_address, "dev", "br0"])
        .status()
        .map_err(|e| format!("Failed to set br0 address: {}", e))?;
    Ok(())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let team_name = env::var("TEAM_NAME").expect("TEAM_NAME env var required");
    let config = AppConfig::new().expect("Failed to load config");
    let competition_name = env::var("COMPETITION_NAME").expect("COMPETITION_NAME env var required");
    let competition = config
        .competitions
        .iter()
        .find(|c| c.name == competition_name)
        .expect(
            format!(
                "Competition {} not found in config",
                competition_name.as_str()
            )
            .as_str(),
        );
    let teams: Vec<String> = competition.teams.iter().map(|t| t.name.clone()).collect();
    let team_index = teams
        .iter()
        .position(|n| n == &team_name)
        .expect("TEAM_NAME not found in config");
    let vxlan_id = 1338 + team_index as u32;

    // get the cidr for the team
    let cidr = competition.cidr.as_ref().expect("Competition CIDR missing");
    let base = cidr.split('/').next().expect("Invalid CIDR");
    let mut octets: Vec<u8> = base.split('.').map(|s| s.parse().unwrap()).collect();
    // Set third octet to team_index+1
    octets[2] = (team_index + 1) as u8;
    octets[3] = 0;
    let team_cidr = format!("{}.{}.{}.0", octets[0], octets[1], octets[2]);

    // Use vtep_host from the first competition, fallback to localhost if missing
    if let Err(e) = create_vxlan_interface(&vxlan_id.to_string()) {
        eprintln!("{}", e);
    }
    if let Err(e) = create_bridge(&team_cidr) {
        eprintln!("{}", e);
    }

    // Add route for the competitions's /16 subnet via .1 (first IP in team /24 subnet) on br0
    let route_command = Command::new("ip")
        .args([
            "route",
            "add",
            &format!("{}", competition.cidr.as_ref().unwrap()),
            "via",
            &format!("{}.{}.{}.1", octets[0], octets[1], octets[2]),
            "dev",
            "br0",
        ])
        .status();
    match route_command {
        Ok(status) if status.success() => {
            println!("Route added successfully for competition subnet");
        }
        Ok(_) | Err(_) => {
            eprintln!("Failed to add route for competition subnet");
        }
    }

    // DNS resolution loop for vtep_host
    let remote = competition.vtep_host.as_deref().unwrap_or("127.0.0.1");
    let vtep_host = remote.to_string();
    let last_ip = Arc::new(Mutex::new(None::<IpAddr>));
    let last_ip_clone = last_ip.clone();
    print!(
        "Starting DNS resolution loop for VTEP host: {}\n",
        vtep_host
    );
    tokio::spawn(async move {
        loop {
            match tokio::net::lookup_host((vtep_host.as_str(), 0)).await {
                Ok(mut addrs) => {
                    if let Some(ip) = addrs.next().map(|sockaddr| sockaddr.ip()) {
                        let mut last = last_ip_clone.lock().await;
                        if last.map_or(true, |prev| prev != ip) {
                            // flush the FDB entries for vxlan0
                            let _ = Command::new("bridge")
                                .args(["fdb", "flush", "dev", "vxlan0"])
                                .status();
                            // Only update if IP changed
                            let status = Command::new("bridge")
                                .args([
                                    "fdb",
                                    "append",
                                    "00:00:00:00:00:00",
                                    "dst",
                                    &ip.to_string(),
                                    "dev",
                                    "vxlan0",
                                ])
                                .status();
                            match status {
                                Ok(s) if s.success() => {
                                    *last = Some(ip);
                                    println!("Appended FDB entry for vxlan0 remote {}", ip);
                                }
                                Ok(_) | Err(_) => {
                                    eprintln!(
                                        "Failed to append FDB entry for vxlan0 remote {}",
                                        ip
                                    );
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    eprintln!("DNS resolution error for {}: {}", vtep_host, e);
                }
            }
            sleep(Duration::from_secs(10)).await;
        }
    });

    // Health check state
    let health_status: SharedHealth = Arc::new(Mutex::new(HealthStatus::Healthy));
    let health_status_clone = health_status.clone();
    let first_ip = format!("{}.{}.{}.1", octets[0], octets[1], octets[2]);
    tokio::spawn(async move {
        let mut fail_count = 0;
        loop {
            let output = Command::new("ping")
                .args(["-c", "1", "-W", "2", &first_ip])
                .output();
            match output {
                Ok(out) if out.status.success() => {
                    if fail_count > 3 {
                        println!(
                            "Ping to {} successful after {} failures",
                            first_ip, fail_count
                        );
                    }
                    fail_count = 0;
                    let mut status = health_status_clone.lock().await;
                    *status = HealthStatus::Healthy;
                }
                _ => {
                    fail_count += 1;
                    if fail_count >= 3 {
                        let mut status = health_status_clone.lock().await;
                        *status = HealthStatus::Unhealthy;
                        eprintln!(
                            "Ping to {} failed {} times, marking as Unhealthy",
                            first_ip, fail_count
                        );
                    }
                }
            }
            sleep(Duration::from_secs(10)).await;
        }
    });

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(health_status.clone()))
            .service(health)
    })
    .bind(("0.0.0.0", 8000))?
    .run()
    .await
}

I expected to see this happen:
cargo clippy fixes the warnings and doesn't error out.

Instead, this happened:
cargo clippy failed out with above error.

Version

rustc 1.87.0 (17067e9ac 2025-05-09)
binary: rustc
commit-hash: 17067e9ac6d7ecb70e50f92c1944e545188d2359
commit-date: 2025-05-09
host: x86_64-unknown-linux-gnu
release: 1.87.0
LLVM version: 20.1.1

Additional Labels

No response

Metadata

Metadata

Assignees

Labels

C-bugCategory: Clippy is not doing the correct thingI-suggestion-causes-errorIssue: The suggestions provided by this Lint cause an ICE/error when applied

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions