Merge rust-bitcoin/rust-bitcoin#4318: Document the `NumOpResult` type

c30a504ea6 units: Document the NumOpResult type (Tobin C. Harding)

Pull request description:

  Document the `NumOpResult` type.

  Note that this includes two new getters on the `NumOpResult`, API hole found during review of the new docs.

  Fix: #4222

ACKs for top commit:
  apoelstra:
    ACK c30a504ea6a5140bdf5667ea42b76bdfa2457456; successfully ran local tests; nice!

Tree-SHA512: ab8d971b74ff4bb06f5737943740c5c748f6313ce1b82798c7d709f8747779efdffe0aa8ed8620afa449fd0dd502b5a2050729a538c51428215972a4f7b6ebf7
This commit is contained in:
merge-script 2025-04-17 11:35:03 +10:00
commit 94abc99fb0
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
1 changed files with 74 additions and 0 deletions

View File

@ -9,6 +9,74 @@ use NumOpResult as R;
use crate::{Amount, FeeRate, SignedAmount, Weight}; use crate::{Amount, FeeRate, SignedAmount, Weight};
/// Result of a mathematical operation on two numeric types. /// Result of a mathematical operation on two numeric types.
///
/// In order to prevent overflow we provide a custom result type that is similar to the normal
/// [`core::result::Result`] but implements mathematical operatons (e.g. [`core::ops::Add`]) so that
/// math operations can be chained ergonomically. This is very similar to how `NaN` works.
///
/// `NumOpResult` is a monadic type that contains `Valid` and `Error` (similar to `Ok` and `Err`).
/// It supports a subset of functions similar to `Result` (e.g. `unwrap`).
///
/// # Examples
///
/// The `NumOpResult` type provides protection against overflow and div-by-zero.
///
/// ## Overflow protection
///
/// ```
/// # use bitcoin_units::{amount, Amount};
/// // Example UTXO value.
/// let a1 = Amount::from_sat(1_000_000)?;
/// // And another value from some other UTXO.
/// let a2 = Amount::from_sat(765_432)?;
/// // Just an example (typically one would calculate fee using weight and fee rate).
/// let fee = Amount::from_sat(1_00)?;
/// // The amount we want to send.
/// let spend = Amount::from_sat(1_200_000)?;
///
/// // We can error if the change calculation overflows.
/// //
/// // For example if the `spend` value comes from the user and the `change` value is later
/// // used then overflow here could be an attack vector.
/// let change = (a1 + a2 - spend - fee).into_result().expect("handle this error");
///
/// // Or if we control all the values and know they are sane we can just `unwrap`.
/// let change = (a1 + a2 - spend - fee).unwrap();
/// // `NumOpResult` also implements `expect`.
/// let change = (a1 + a2 - spend - fee).expect("we know values don't overflow");
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
///
/// ## Divide-by-zero (overflow in `Div` or `Rem`)
///
/// In some instances one may wish to differentiate div-by-zero from overflow.
///
/// ```
/// # use bitcoin_units::{amount, Amount, FeeRate, NumOpResult, NumOpError};
/// // Two amounts that will be added to calculate the max fee.
/// let a = Amount::from_sat(123).expect("valid amount");
/// let b = Amount::from_sat(467).expect("valid amount");
/// // Fee rate for transaction.
/// let fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
///
/// // Somewhat contrived example to show addition operator chained with division.
/// let max_fee = a + b;
/// let _fee = match max_fee / fee_rate {
/// NumOpResult::Valid(fee) => fee,
/// NumOpResult::Error(e) if e.is_div_by_zero() => {
/// // Do something when div by zero.
/// return Err(e);
/// },
/// NumOpResult::Error(e) => {
/// // We separate div-by-zero from overflow in case it needs to be handled separately.
/// //
/// // This branch could be hit since `max_fee` came from some previous calculation. And if
/// // an input to that calculation was from the user then overflow could be an attack vector.
/// return Err(e);
/// }
/// };
/// # Ok::<_, NumOpError>(())
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[must_use] #[must_use]
pub enum NumOpResult<T> { pub enum NumOpResult<T> {
@ -136,6 +204,12 @@ impl NumOpError {
/// Creates a [`NumOpError`] caused by `op`. /// Creates a [`NumOpError`] caused by `op`.
pub fn while_doing(op: MathOp) -> Self { NumOpError(op) } pub fn while_doing(op: MathOp) -> Self { NumOpError(op) }
/// Returns `true` if this operation error'ed due to overflow.
pub fn is_overflow(self) -> bool { self.0.is_overflow() }
/// Returns `true` if this operation error'ed due to division by zero.
pub fn is_div_by_zero(self) -> bool { self.0.is_div_by_zero() }
/// Returns the [`MathOp`] that caused this error. /// Returns the [`MathOp`] that caused this error.
pub fn operation(self) -> MathOp { self.0 } pub fn operation(self) -> MathOp { self.0 }
} }