refactor: replace unwrap_or with unwrap_or_else for improved error handling (#5163)

In Rust, the `or` and `or_else` methods have distinct behavioral differences. The `or` method always eagerly evaluates its argument and executes any associated function calls. This can lead to unnecessary performance costs—especially in expensive operations like string processing or file handling—and may even trigger unintended side effects.

In contrast, `or_else` evaluates its closure lazily, only when necessary. Introducing a Clippy lint to disallow `or` sacrifices a bit of code simplicity but ensures predictable behavior and enforces lazy evaluation for better performance.
This commit is contained in:
Tunglies
2025-10-22 17:33:55 +08:00
committed by GitHub
parent a05ea64bcd
commit 2d2167e048
18 changed files with 70 additions and 52 deletions

View File

@@ -221,3 +221,5 @@ needless_raw_string_hashes = "deny" # Too many in existing code
#pedantic = { level = "allow", priority = -1 } #pedantic = { level = "allow", priority = -1 }
#nursery = { level = "allow", priority = -1 } #nursery = { level = "allow", priority = -1 }
#restriction = { level = "allow", priority = -1 } #restriction = { level = "allow", priority = -1 }
or_fun_call = "deny"

View File

@@ -1,6 +1,6 @@
use super::CmdResult; use super::CmdResult;
use crate::{cmd::StringifyErr, config::*, core::CoreManager, log_err}; use crate::{cmd::StringifyErr, config::*, core::CoreManager, log_err};
use anyhow::Context; use anyhow::{Context, anyhow};
use serde_yaml_ng::Mapping; use serde_yaml_ng::Mapping;
use smartstring::alias::String; use smartstring::alias::String;
use std::collections::HashMap; use std::collections::HashMap;
@@ -19,7 +19,7 @@ pub async fn get_runtime_yaml() -> CmdResult<String> {
let config = runtime.config.as_ref(); let config = runtime.config.as_ref();
config config
.ok_or(anyhow::anyhow!("failed to parse config to yaml file")) .ok_or_else(|| anyhow!("failed to parse config to yaml file"))
.and_then(|config| { .and_then(|config| {
serde_yaml_ng::to_string(config) serde_yaml_ng::to_string(config)
.context("failed to convert config to yaml") .context("failed to convert config to yaml")
@@ -48,7 +48,7 @@ pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> Cm
let config = runtime let config = runtime
.config .config
.as_ref() .as_ref()
.ok_or(anyhow::anyhow!("failed to parse config to yaml file")) .ok_or_else(|| anyhow!("failed to parse config to yaml file"))
.stringify_err()?; .stringify_err()?;
if let Some(serde_yaml_ng::Value::Sequence(proxies)) = config.get("proxies") { if let Some(serde_yaml_ng::Value::Sequence(proxies)) = config.get("proxies") {

View File

@@ -287,7 +287,7 @@ impl IClashTemp {
} }
None => None, None => None,
}) })
.unwrap_or("127.0.0.1:9097".into()) .unwrap_or_else(|| "127.0.0.1:9097".into())
} }
pub fn guard_external_controller(config: &Mapping) -> String { pub fn guard_external_controller(config: &Mapping) -> String {

View File

@@ -140,7 +140,7 @@ impl Config {
.latest_ref() .latest_ref()
.config .config
.as_ref() .as_ref()
.ok_or(anyhow!("failed to get runtime config"))? .ok_or_else(|| anyhow!("failed to get runtime config"))?
.clone(); .clone();
drop(runtime); // 显式释放锁 drop(runtime); // 显式释放锁

View File

@@ -164,8 +164,8 @@ impl PrfItem {
PrfItem::from_url(url, name, desc, item.option).await PrfItem::from_url(url, name, desc, item.option).await
} }
"local" => { "local" => {
let name = item.name.unwrap_or("Local File".into()); let name = item.name.unwrap_or_else(|| "Local File".into());
let desc = item.desc.unwrap_or("".into()); let desc = item.desc.unwrap_or_else(|| "".into());
PrfItem::from_local(name, desc, file_data, item.option).await PrfItem::from_local(name, desc, file_data, item.option).await
} }
typ => bail!("invalid profile item type \"{typ}\""), typ => bail!("invalid profile item type \"{typ}\""),
@@ -235,7 +235,7 @@ impl PrfItem {
}), }),
home: None, home: None,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), file_data: Some(file_data.unwrap_or_else(|| tmpl::ITEM_LOCAL.into())),
}) })
} }
@@ -331,7 +331,8 @@ impl PrfItem {
} }
} }
None => Some( None => Some(
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()), crate::utils::help::get_last_part_and_decode(url)
.unwrap_or_else(|| "Remote File".into()),
), ),
}; };
let update_interval = match update_interval { let update_interval = match update_interval {
@@ -355,7 +356,7 @@ impl PrfItem {
let uid = help::get_uid("R").into(); let uid = help::get_uid("R").into();
let file = format!("{uid}.yaml").into(); let file = format!("{uid}.yaml").into();
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()).into()); let name = name.unwrap_or_else(|| filename.unwrap_or_else(|| "Remote File".into()).into());
let data = resp.text_with_charset()?; let data = resp.text_with_charset()?;
// process the charset "UTF-8 with BOM" // process the charset "UTF-8 with BOM"

View File

@@ -241,8 +241,11 @@ impl IProfiles {
// move the field value after save // move the field value after save
if let Some(file_data) = item.file_data.take() { if let Some(file_data) = item.file_data.take() {
let file = each.file.take(); let file = each.file.take();
let file = file let file = file.unwrap_or_else(|| {
.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid).into())); item.file
.take()
.unwrap_or_else(|| format!("{}.yaml", &uid).into())
});
// the file must exists // the file must exists
each.file = Some(file.clone()); each.file = Some(file.clone());

View File

@@ -32,11 +32,11 @@ impl IRuntime {
let patch_tun = patch.get("tun"); let patch_tun = patch.get("tun");
if patch_tun.is_some() { if patch_tun.is_some() {
let tun = config.get("tun"); let tun = config.get("tun");
let mut tun: Mapping = tun.map_or(Mapping::new(), |val| { let mut tun: Mapping = tun.map_or_else(Mapping::new, |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or_else(Mapping::new)
}); });
let patch_tun = patch_tun.map_or(Mapping::new(), |val| { let patch_tun = patch_tun.map_or_else(Mapping::new, |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or_else(Mapping::new)
}); });
use_keys(&patch_tun).into_iter().for_each(|key| { use_keys(&patch_tun).into_iter().for_each(|key| {
if let Some(value) = patch_tun.get(key.as_str()) { if let Some(value) = patch_tun.get(key.as_str()) {

View File

@@ -330,7 +330,7 @@ async fn check_service_version() -> Result<String> {
return Err(anyhow::anyhow!(err_msg)); return Err(anyhow::anyhow!(err_msg));
} }
let version = response.data.unwrap_or("unknown".into()); let version = response.data.unwrap_or_else(|| "unknown".into());
Ok(version) Ok(version)
}; };

View File

@@ -84,7 +84,10 @@ impl TrayState {
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let tray_icon_colorful = verge.tray_icon.unwrap_or("monochrome".into()); let tray_icon_colorful = verge
.tray_icon
.clone()
.unwrap_or_else(|| "monochrome".into());
if tray_icon_colorful == "monochrome" { if tray_icon_colorful == "monochrome" {
( (
false, false,
@@ -118,7 +121,10 @@ impl TrayState {
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".into()); let tray_icon_colorful = verge
.tray_icon
.clone()
.unwrap_or_else(|| "monochrome".into());
if tray_icon_colorful == "monochrome" { if tray_icon_colorful == "monochrome" {
( (
false, false,
@@ -152,7 +158,10 @@ impl TrayState {
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".into()); let tray_icon_colorful = verge
.tray_icon
.clone()
.unwrap_or_else(|| "monochrome".into());
if tray_icon_colorful == "monochrome" { if tray_icon_colorful == "monochrome" {
( (
false, false,
@@ -219,7 +228,7 @@ impl Tray {
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_ref().tray_event.clone() };
let tray_event = tray_event.unwrap_or("main_window".into()); let tray_event = tray_event.unwrap_or_else(|| "main_window".into());
let tray = app_handle let tray = app_handle
.tray_by_id("main") .tray_by_id("main")
.ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?; .ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?;
@@ -350,7 +359,10 @@ impl Tray {
(false, false) => TrayState::get_common_tray_icon().await, (false, false) => TrayState::get_common_tray_icon().await,
}; };
let colorful = verge.tray_icon.clone().unwrap_or("monochrome".into()); let colorful = verge
.tray_icon
.clone()
.unwrap_or_else(|| "monochrome".into());
let is_colorful = colorful == "colorful"; let is_colorful = colorful == "colorful";
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?)); let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
@@ -448,9 +460,10 @@ impl Tray {
let profile_text = t("Profile").await; let profile_text = t("Profile").await;
let v = env!("CARGO_PKG_VERSION"); let v = env!("CARGO_PKG_VERSION");
let reassembled_version = v.split_once('+').map_or(v.into(), |(main, rest)| { let reassembled_version = v.split_once('+').map_or_else(
format!("{main}+{}", rest.split('.').next().unwrap_or("")) || v.into(),
}); |(main, rest)| format!("{main}+{}", rest.split('.').next().unwrap_or("")),
);
let tooltip = format!( let tooltip = format!(
"Clash Verge {}\n{}: {}\n{}: {}\n{}: {}", "Clash Verge {}\n{}: {}\n{}: {}\n{}: {}",
@@ -505,7 +518,7 @@ impl Tray {
#[cfg(any(target_os = "macos", target_os = "windows"))] #[cfg(any(target_os = "macos", target_os = "windows"))]
let show_menu_on_left_click = { let show_menu_on_left_click = {
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_ref().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
tray_event.as_str() == "tray_menu" tray_event.as_str() == "tray_menu"
}; };
@@ -526,7 +539,7 @@ impl Tray {
tray.on_tray_icon_event(|_app_handle, event| { tray.on_tray_icon_event(|_app_handle, event| {
AsyncHandler::spawn(|| async move { AsyncHandler::spawn(|| async move {
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_ref().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
log::debug!(target: "app", "tray event: {tray_event:?}"); log::debug!(target: "app", "tray event: {tray_event:?}");
if let TrayIconEvent::Click { if let TrayIconEvent::Click {

View File

@@ -73,7 +73,7 @@ impl AsyncChainItemFrom for Option<ChainItem> {
async fn from_async(item: &PrfItem) -> Option<ChainItem> { async fn from_async(item: &PrfItem) -> Option<ChainItem> {
let itype = item.itype.as_ref()?.as_str(); let itype = item.itype.as_ref()?.as_str();
let file = item.file.clone()?; let file = item.file.clone()?;
let uid = item.uid.clone().unwrap_or("".into()); let uid = item.uid.clone().unwrap_or_else(|| "".into());
let path = dirs::app_profiles_dir().ok()?.join(file.as_str()); let path = dirs::app_profiles_dir().ok()?.join(file.as_str());
if !path.exists() { if !path.exists() {

View File

@@ -296,10 +296,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// 合并默认的config // 合并默认的config
for (key, value) in clash_config.into_iter() { for (key, value) in clash_config.into_iter() {
if key.as_str() == Some("tun") { if key.as_str() == Some("tun") {
let mut tun = config.get_mut("tun").map_or(Mapping::new(), |val| { let mut tun = config.get_mut("tun").map_or_else(Mapping::new, |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or_else(Mapping::new)
}); });
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new()); let patch_tun = value.as_mapping().cloned().unwrap_or_else(Mapping::new);
for (key, value) in patch_tun.into_iter() { for (key, value) in patch_tun.into_iter() {
tun.insert(key, value); tun.insert(key, value);
} }

View File

@@ -24,8 +24,8 @@ macro_rules! append {
pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
let tun_key = Value::from("tun"); let tun_key = Value::from("tun");
let tun_val = config.get(&tun_key); let tun_val = config.get(&tun_key);
let mut tun_val = tun_val.map_or(Mapping::new(), |val| { let mut tun_val = tun_val.map_or_else(Mapping::new, |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or_else(Mapping::new)
}); });
if enable { if enable {
@@ -52,8 +52,8 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 读取DNS配置 // 读取DNS配置
let dns_key = Value::from("dns"); let dns_key = Value::from("dns");
let dns_val = config.get(&dns_key); let dns_val = config.get(&dns_key);
let mut dns_val = dns_val.map_or(Mapping::new(), |val| { let mut dns_val = dns_val.map_or_else(Mapping::new, |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or_else(Mapping::new)
}); });
let ipv6_key = Value::from("ipv6"); let ipv6_key = Value::from("ipv6");
let ipv6_val = config let ipv6_val = config

View File

@@ -33,9 +33,9 @@ impl Debug for PlatformSpecification {
impl PlatformSpecification { impl PlatformSpecification {
pub fn new() -> Self { pub fn new() -> Self {
let system_name = System::name().unwrap_or("Null".into()); let system_name = System::name().unwrap_or_else(|| "Null".into());
let system_version = System::long_os_version().unwrap_or("Null".into()); let system_version = System::long_os_version().unwrap_or_else(|| "Null".into());
let system_kernel_version = System::kernel_version().unwrap_or("Null".into()); let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into());
let system_arch = System::cpu_arch(); let system_arch = System::cpu_arch();
let handler = handle::Handle::app_handle(); let handler = handle::Handle::app_handle();

View File

@@ -47,7 +47,7 @@ pub fn app_home_dir() -> Result<PathBuf> {
let app_exe = dunce::canonicalize(app_exe)?; let app_exe = dunce::canonicalize(app_exe)?;
let app_dir = app_exe let app_dir = app_exe
.parent() .parent()
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?; .ok_or_else(|| anyhow::anyhow!("failed to get the portable app dir"))?;
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID)); return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
} }
@@ -170,7 +170,7 @@ pub fn path_to_str(path: &PathBuf) -> Result<&str> {
let path_str = path let path_str = path
.as_os_str() .as_os_str()
.to_str() .to_str()
.ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?; .ok_or_else(|| anyhow::anyhow!("failed to get path from {:?}", path))?;
Ok(path_str) Ok(path_str)
} }

View File

@@ -34,10 +34,9 @@ pub async fn read_mapping(path: &PathBuf) -> Result<Mapping> {
Ok(val Ok(val
.as_mapping() .as_mapping()
.ok_or(anyhow!( .ok_or_else(|| {
"failed to transform to yaml mapping \"{}\"", anyhow!("failed to transform to yaml mapping \"{}\"", path.display())
path.display() })?
))?
.to_owned()) .to_owned())
} }
Err(err) => { Err(err) => {

View File

@@ -155,9 +155,9 @@ pub async fn delete_log() -> Result<()> {
let month = u32::from_str(sa[1])?; let month = u32::from_str(sa[1])?;
let day = u32::from_str(sa[2])?; let day = u32::from_str(sa[2])?;
let time = chrono::NaiveDate::from_ymd_opt(year, month, day) let time = chrono::NaiveDate::from_ymd_opt(year, month, day)
.ok_or(anyhow::anyhow!("invalid time str"))? .ok_or_else(|| anyhow::anyhow!("invalid time str"))?
.and_hms_opt(0, 0, 0) .and_hms_opt(0, 0, 0)
.ok_or(anyhow::anyhow!("invalid time str"))?; .ok_or_else(|| anyhow::anyhow!("invalid time str"))?;
Ok(time) Ok(time)
}; };
@@ -171,7 +171,7 @@ pub async fn delete_log() -> Result<()> {
let file_time = Local let file_time = Local
.from_local_datetime(&created_time) .from_local_datetime(&created_time)
.single() .single()
.ok_or(anyhow::anyhow!("invalid local datetime"))?; .ok_or_else(|| anyhow::anyhow!("invalid local datetime"))?;
let duration = now.signed_duration_since(file_time); let duration = now.signed_duration_since(file_time);
if duration.num_days() > day { if duration.num_days() > day {
@@ -523,7 +523,7 @@ pub async fn startup_script() -> Result<()> {
let script_path = { let script_path = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_ref();
verge.startup_script.clone().unwrap_or("".into()) verge.startup_script.clone().unwrap_or_else(|| "".into())
}; };
if script_path.is_empty() { if script_path.is_empty() {
@@ -547,7 +547,7 @@ pub async fn startup_script() -> Result<()> {
} }
let parent_dir = script_dir.parent(); let parent_dir = script_dir.parent();
let working_dir = parent_dir.unwrap_or(script_dir.as_ref()); let working_dir = parent_dir.unwrap_or_else(|| script_dir.as_ref());
app_handle app_handle
.shell() .shell()

View File

@@ -26,7 +26,7 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
.latest_ref() .latest_ref()
.start_page .start_page
.clone() .clone()
.unwrap_or("/".into()); .unwrap_or_else(|| "/".into());
match tauri::WebviewWindowBuilder::new( match tauri::WebviewWindowBuilder::new(
app_handle, app_handle,
"main", /* the unique window label */ "main", /* the unique window label */

View File

@@ -88,12 +88,12 @@ pub fn embed_server() {
.latest_ref() .latest_ref()
.pac_file_content .pac_file_content
.clone() .clone()
.unwrap_or(DEFAULT_PAC.into()); .unwrap_or_else(|| DEFAULT_PAC.into());
let pac_port = verge_config let pac_port = verge_config
.latest_ref() .latest_ref()
.verge_mixed_port .verge_mixed_port
.unwrap_or(clash_config.latest_ref().get_mixed_port()); .unwrap_or_else(|| clash_config.latest_ref().get_mixed_port());
let pac = warp::path!("commands" / "pac").map(move || { let pac = warp::path!("commands" / "pac").map(move || {
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}")); let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));