Skip to content

Conversation

@phip1611
Copy link
Member

@phip1611 phip1611 commented Nov 26, 2025

Quick follow-up to #7493. I found a pragmatic way to enable needless_pass_by_value across the entire repository: we can keep a small set of targeted #[allow] annotations in places where passing by value is intentional (or not worth the consequences), while still linting the rest of the codebase. With this approach I was able to remove dozens of unnecessary clones, and I also discovered several functions that took Arc or Rc by value despite never using any of the smartpointer functionality.

Given the clear improvements to overall code quality, I believe this change is worthwhile

Part of #7489

@phip1611 phip1611 self-assigned this Nov 26, 2025
@phip1611 phip1611 requested a review from a team as a code owner November 26, 2025 13:22
@phip1611 phip1611 force-pushed the clippy branch 10 times, most recently from 399af4b to 1022470 Compare November 26, 2025 16:24
This is a follow-up of [0].

# Advantages

- This saves dozens of unneeded clone()s across the whole code base
- Makes it much easier to reason about how parameters are used
  (often we passed owned Arc/Rc versions without actually needing
  ownership)

# Exceptions

For certain code paths, the alternatives would require awkward or overly
complex code, and in some cases the functions are the logical owners of
the values they take. In those cases, I've added
#[allow(clippy::needless_pass_by_value)].

This does not mean that one should not improve this in the future.

[0] 6a86c15

Signed-off-by: Philipp Schuster <[email protected]>
On-behalf-of: SAP [email protected]
`impl AsRef<Path>` is the most idiomatic way to consume paths
in Rust. I removed the mixture.

Signed-off-by: Philipp Schuster <[email protected]>
On-behalf-of: SAP [email protected]
Copy link
Member

@rbradford rbradford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

fd: RawFd,
f: F,
original_termios_opt: Arc<Mutex<Option<termios>>>,
original_termios_opt: &Mutex<Option<termios>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it ever make sense to have a Mutex without an Arc ?

Copy link
Member Author

@phip1611 phip1611 Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller still has Arc<Mutex<T>>. The idea here is that the function doesn't need any of Arc's abstractions, for example no clone(). So I reduced the parameters to what is actually used and needed. It is more explicit and we know there are no clones() to expect in such cases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A clone on an Arc isn't bad - it's only a refcount increase - I don't have a reference to it now but I think some in the Rust community regret overloading Clone for that purpose on Arc.

I just worry that this might increase the cognitive load - the Arc<Mutex<..>> pattern is so common.

Copy link
Member Author

@phip1611 phip1611 Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A clone on an Arc isn't bad - it's only a refcount increase

Yes. My idea was the following: Having &T instead of &Arc<T> helps every developer to understand that this code won't create further strong or weak references. It's not really about the costs of the cheap clones.

Arc<Mutex<..>>

I see your concern! But we will keep Arc<Mutex<..>> in most places. Just some called functions are affected by that in case they never create a strong or weak reference.

(I don't have a strong opinion on this, but a tendency that this is benefictial :)

}

#[allow(clippy::needless_pass_by_value)]
fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, could this be cmd_arguments: &ArgMatches ? I'm sure there was a reason why you didn't change it - can you elaborate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be, yes. Here, I had a feeling of start_vmm should logically own and consume the type. No strong opinion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay - I get it. I think we can move forward with this PR.

@rbradford rbradford added this pull request to the merge queue Nov 27, 2025
Merged via the queue into cloud-hypervisor:main with commit afcb2b2 Nov 27, 2025
45 checks passed
phip1611 added a commit to phip1611/cloud-hypervisor that referenced this pull request Dec 10, 2025
In [0] we refactored some Arc<Mutex<T>> parameters to &Mutex<T>> to
satisfy clippy's needless_pass_by_value lint. Nevertheless, this is also
not so idiomatic, so as a follow-up, we put the responsibility to lock
objects to the caller side (only where this is not strictly needed by
the callee).

While on it, I also tried to pass vm_config directly into
pre_create_console_devices() which would clean up some code, but then
we have interleaving mutable and immutable borrows of the Vmm.

This was my attempt:

```patch
diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs
index 6e1ef20..105d005dc 100644
--- a/vmm/src/console_devices.rs
+++ b/vmm/src/console_devices.rs
@@ -24,7 +24,7 @@ use thiserror::Error;

 use crate::Vmm;
 use crate::sigwinch_listener::listen_for_sigwinch_on_tty;
-use crate::vm_config::ConsoleOutputMode;
+use crate::vm_config::{ConsoleOutputMode, VmConfig};

 const TIOCSPTLCK: libc::c_int = 0x4004_5431;
 const TIOCGPTPEER: libc::c_int = 0x5441;
diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs
index 88084cb..1ef6d7b63 100644
--- a/vmm/src/lib.rs
+++ b/vmm/src/lib.rs
@@ -1002,9 +1002,16 @@ impl Vmm {

         let config = vm_migration_config.vm_config.clone();
         self.vm_config = Some(vm_migration_config.vm_config);
-        self.console_info = Some(pre_create_console_devices(self).map_err(|e| {
-            MigratableError::MigrateReceive(anyhow!("Error creating console devices: {e:?}"))
-        })?);
+        self.console_info = {
+            let mut vmconfig = self.vm_config.as_ref().unwrap().lock().unwrap();
+            Some(
+                pre_create_console_devices(self, &mut *vmconfig).map_err(|e| {
+                    MigratableError::MigrateReceive(anyhow!(
+                        "Error creating console devices: {e:?}"
+                    ))
+                })?,
+            )
+        };

         if self
             .vm_config
@@ -1443,7 +1450,7 @@ impl Vmm {
         self.vm_check_cpuid_compatibility(&vm_config, &vm_snapshot.common_cpuid)
             .map_err(VmError::Restore)?;

-        self.vm_config = Some(Arc::clone(&vm_config));
+        self.vm_config = Some(vm_config);

         // Always re-populate the 'console_info' based on the new 'vm_config'
         self.console_info =
@@ -1613,25 +1620,23 @@ fn apply_landlock(vm_config: &mut VmConfig) -> result::Result<(), LandlockError>
 }

 impl RequestHandler for Vmm {
-    fn vm_create(&mut self, config: Box<VmConfig>) -> result::Result<(), VmError> {
+    fn vm_create(&mut self, mut config: Box<VmConfig>) -> result::Result<(), VmError> {
         // We only store the passed VM config.
         // The VM will be created when being asked to boot it.
         if self.vm_config.is_some() {
             return Err(VmError::VmAlreadyCreated);
         }

-        self.vm_config = Some(Arc::new(Mutex::new(*config)));
-        self.console_info =
-            Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?);
+        self.console_info = Some(
+            pre_create_console_devices(self, &mut *config)
+                .map_err(VmError::CreateConsoleDevices)?,
+        );

-        if self
-            .vm_config
-            .as_ref()
-            .is_some_and(|config| config.lock().unwrap().landlock_enable)
-        {
-            let mut config = self.vm_config.as_ref().unwrap().lock().unwrap();
+        if config.landlock_enable {
             apply_landlock(&mut *config).map_err(VmError::ApplyLandlock)?;
         }
+
+        self.vm_config = Some(Arc::new(Mutex::new(*config)));
         Ok(())
     }

@@ -1648,8 +1653,11 @@ impl RequestHandler for Vmm {

             // console_info is set to None in vm_shutdown. re-populate here if empty
             if self.console_info.is_none() {
-                self.console_info =
-                    Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?);
+                let mut vmconfig = self.vm_config.as_ref().unwrap().lock().unwrap();
+                self.console_info = Some(
+                    pre_create_console_devices(self, &mut *vmconfig)
+                        .map_err(VmError::CreateConsoleDevices)?,
+                );
             }

             // Create a new VM if we don't have one yet.

```

[0] cloud-hypervisor#7519

Signed-off-by: Philipp Schuster <[email protected]>
On-behalf-of: SAP [email protected]
phip1611 added a commit to phip1611/cloud-hypervisor that referenced this pull request Dec 10, 2025
In [0] we refactored some Arc<Mutex<T>> parameters to &Mutex<T>> to
satisfy clippy's needless_pass_by_value lint. Nevertheless, this is also
not so idiomatic, so as a follow-up, we put the responsibility to lock
objects to the caller side (only where this is not strictly needed by
the callee).

While on it, I also tried to pass vm_config directly into
pre_create_console_devices() which would clean up some code, but then
we have interleaving mutable and immutable borrows of the Vmm.

This was my attempt:

```patch
diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs
index 6e1ef20..105d005dc 100644
--- a/vmm/src/console_devices.rs
+++ b/vmm/src/console_devices.rs
@@ -24,7 +24,7 @@ use thiserror::Error;

 use crate::Vmm;
 use crate::sigwinch_listener::listen_for_sigwinch_on_tty;
-use crate::vm_config::ConsoleOutputMode;
+use crate::vm_config::{ConsoleOutputMode, VmConfig};

 const TIOCSPTLCK: libc::c_int = 0x4004_5431;
 const TIOCGPTPEER: libc::c_int = 0x5441;
diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs
index 88084cb..1ef6d7b63 100644
--- a/vmm/src/lib.rs
+++ b/vmm/src/lib.rs
@@ -1002,9 +1002,16 @@ impl Vmm {

         let config = vm_migration_config.vm_config.clone();
         self.vm_config = Some(vm_migration_config.vm_config);
-        self.console_info = Some(pre_create_console_devices(self).map_err(|e| {
-            MigratableError::MigrateReceive(anyhow!("Error creating console devices: {e:?}"))
-        })?);
+        self.console_info = {
+            let mut vmconfig = self.vm_config.as_ref().unwrap().lock().unwrap();
+            Some(
+                pre_create_console_devices(self, &mut *vmconfig).map_err(|e| {
+                    MigratableError::MigrateReceive(anyhow!(
+                        "Error creating console devices: {e:?}"
+                    ))
+                })?,
+            )
+        };

         if self
             .vm_config
@@ -1443,7 +1450,7 @@ impl Vmm {
         self.vm_check_cpuid_compatibility(&vm_config, &vm_snapshot.common_cpuid)
             .map_err(VmError::Restore)?;

-        self.vm_config = Some(Arc::clone(&vm_config));
+        self.vm_config = Some(vm_config);

         // Always re-populate the 'console_info' based on the new 'vm_config'
         self.console_info =
@@ -1613,25 +1620,23 @@ fn apply_landlock(vm_config: &mut VmConfig) -> result::Result<(), LandlockError>
 }

 impl RequestHandler for Vmm {
-    fn vm_create(&mut self, config: Box<VmConfig>) -> result::Result<(), VmError> {
+    fn vm_create(&mut self, mut config: Box<VmConfig>) -> result::Result<(), VmError> {
         // We only store the passed VM config.
         // The VM will be created when being asked to boot it.
         if self.vm_config.is_some() {
             return Err(VmError::VmAlreadyCreated);
         }

-        self.vm_config = Some(Arc::new(Mutex::new(*config)));
-        self.console_info =
-            Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?);
+        self.console_info = Some(
+            pre_create_console_devices(self, &mut *config)
+                .map_err(VmError::CreateConsoleDevices)?,
+        );

-        if self
-            .vm_config
-            .as_ref()
-            .is_some_and(|config| config.lock().unwrap().landlock_enable)
-        {
-            let mut config = self.vm_config.as_ref().unwrap().lock().unwrap();
+        if config.landlock_enable {
             apply_landlock(&mut *config).map_err(VmError::ApplyLandlock)?;
         }
+
+        self.vm_config = Some(Arc::new(Mutex::new(*config)));
         Ok(())
     }

@@ -1648,8 +1653,11 @@ impl RequestHandler for Vmm {

             // console_info is set to None in vm_shutdown. re-populate here if empty
             if self.console_info.is_none() {
-                self.console_info =
-                    Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?);
+                let mut vmconfig = self.vm_config.as_ref().unwrap().lock().unwrap();
+                self.console_info = Some(
+                    pre_create_console_devices(self, &mut *vmconfig)
+                        .map_err(VmError::CreateConsoleDevices)?,
+                );
             }

             // Create a new VM if we don't have one yet.

```

[0] cloud-hypervisor#7519

Signed-off-by: Philipp Schuster <[email protected]>
On-behalf-of: SAP [email protected]
@phip1611 phip1611 deleted the clippy branch December 12, 2025 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants