admin管理员组文章数量:1126094
I'm working on a function that converts a json value into an i32
, returning an error if any step of the conversion fails.
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
let err_msg = "Invalid height".to_string();
let height = match height {
Value::Number(h) => Ok(h),
_ => Err(RPCError::CustomError(1, err_msg.clone())),
}?;
let height = height
.as_i64()
.ok_or(RPCError::CustomError(1, err_msg.clone()))?;
if height < 0 {
return Err(RPCError::CustomError(1, err_msg));
}
i32::try_from(height).map_err(|_| RPCError::CustomError(1, err_msg))
}
Is there any good way to prevent the unnecessary cloning of the error message in the match
branch and in the ok_or
argument, short of unrolling the whole code with explicit if...else...
branches? Technically if any of these error conditions are met the following code will be unreachable, so we should never really need to move this String in a perfect world.
I tried replacing the ok_or(RPCError::CustomError(1, err_msg.clone())
method with ok_or_else(|| RPCError::CustomError(1, err_msg.clone())
, but the err_msg still seems to be captured and moved in the closure.
Bonus question: would there be any performance improvement by making the code more verbose with explicit branching to avoid the unnecessary copy, or is the "idiomatic rust" solution more performant despite these copies?
I'm working on a function that converts a json value into an i32
, returning an error if any step of the conversion fails.
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
let err_msg = "Invalid height".to_string();
let height = match height {
Value::Number(h) => Ok(h),
_ => Err(RPCError::CustomError(1, err_msg.clone())),
}?;
let height = height
.as_i64()
.ok_or(RPCError::CustomError(1, err_msg.clone()))?;
if height < 0 {
return Err(RPCError::CustomError(1, err_msg));
}
i32::try_from(height).map_err(|_| RPCError::CustomError(1, err_msg))
}
Is there any good way to prevent the unnecessary cloning of the error message in the match
branch and in the ok_or
argument, short of unrolling the whole code with explicit if...else...
branches? Technically if any of these error conditions are met the following code will be unreachable, so we should never really need to move this String in a perfect world.
I tried replacing the ok_or(RPCError::CustomError(1, err_msg.clone())
method with ok_or_else(|| RPCError::CustomError(1, err_msg.clone())
, but the err_msg still seems to be captured and moved in the closure.
Bonus question: would there be any performance improvement by making the code more verbose with explicit branching to avoid the unnecessary copy, or is the "idiomatic rust" solution more performant despite these copies?
Share Improve this question edited 2 days ago cafce25 27k5 gold badges41 silver badges52 bronze badges asked 2 days ago PiRKPiRK 1,0553 gold badges13 silver badges33 bronze badges 1 |3 Answers
Reset to default 3This cleans up really nicely using the functional combination methods (Option::and_then
), you can also use Value::as_i64
directly instead of going through an intermediary &Number
:
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
height
.as_i64()
.and_then(|height| (height >= 0).then_some(height))
.and_then(|height| i32::try_from(height).ok())
.ok_or_else(|| RPCError::CustomError(1, "Invalid height".to_string()))
}
Now there is only one place where you construct the error still and thus no problem with cloning or moving.
Because I use ok_or_else
at the end this code also doesn't have to allocate the String
in case everything works out ok, which avoids another unnecessary allocation.
In addition to the other response (which shows how to move instead of cloning), you should consider not creating a String
at all before the error occurs. Right now, your function always allocates memory for the error message, even if the function succeeds. Using a string slice first would solve your problem as well.
Besides that, the idiomatic way is to seperate the error and message entirely using the Display
trait. Check Rust By Example for an example.
Generation of an error is completely separate from how it is displayed. There's no need to be concerned about cluttering complex logic with the display style.
Explicit clones can be removed like so, though I don't expect this to have any performance implications:
#![allow(dead_code)]
enum RPCError {
CustomError(usize, String),
}
use serde_json::Value; // 1.0.133
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
let err_msg = "Invalid height".to_string();
let height = match height {
Value::Number(h) => h,
_ => return Err(RPCError::CustomError(1, err_msg)),
};
let height = match height.as_i64() {
Some(h) => h,
None => return Err(RPCError::CustomError(1, err_msg)),
};
if height < 0 {
return Err(RPCError::CustomError(1, err_msg));
}
i32::try_from(height).map_err(|_| RPCError::CustomError(1, err_msg))
}
or the more functional:
#![allow(dead_code)]
enum RPCError {
CustomError(usize, String),
}
use serde_json::Value; // 1.0.133
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
let err_msg = "Invalid height".to_string();
match height {
Value::Number(h) => match h.as_i64() {
Some(h) if h >= 0 => i32::try_from(h).map_err(|_| RPCError::CustomError(1, err_msg)),
_ => Err(RPCError::CustomError(1, err_msg)),
},
_ => Err(RPCError::CustomError(1, err_msg)),
}
}
As cafce25 points out in their answer, Value
has a dedicated as_i64
method that does what you want, so this would become:
fn json_to_block_height(height: Value) -> Result<i32, RPCError> {
let err_msg = "Invalid height".to_string();
match height.as_i64() {
Some(h) if h >= 0 => i32::try_from(h).map_err(|_| RPCError::CustomError(1, err_msg)),
_ => Err(RPCError::CustomError(1, err_msg)),
}
}
From this, it is only one more small step to cafce25's excellent answer using and_then
, which is really what you should use instead, once you wrap your head around all the useful methods on Option and Result.
本文标签: rustHow not to capture or move a String in a match branch or okor argumentStack Overflow
版权声明:本文标题:rust - How not to capture or move a String in a match branch or ok_or argument - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736665722a1946651.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
ok_or
is just a function, the compiler can't reason about the scenarios in which it will or won't return early. if-else lets the compiler reason about reachability. – BallpointBen Commented 2 days ago