#[repr(C)]pub struct RiskEngine {Show 30 fields
pub vault: U128,
pub insurance_fund: InsuranceFund,
pub params: RiskParams,
pub current_slot: u64,
pub funding_index_qpb_e6: I128,
pub last_funding_slot: u64,
pub funding_rate_bps_per_slot_last: i64,
pub last_crank_slot: u64,
pub max_crank_staleness_slots: u64,
pub total_open_interest: U128,
pub c_tot: U128,
pub pnl_pos_tot: U128,
pub liq_cursor: u16,
pub gc_cursor: u16,
pub last_full_sweep_start_slot: u64,
pub last_full_sweep_completed_slot: u64,
pub crank_cursor: u16,
pub sweep_start_idx: u16,
pub lifetime_liquidations: u64,
pub lifetime_force_realize_closes: u64,
pub net_lp_pos: I128,
pub lp_sum_abs: U128,
pub lp_max_abs: U128,
pub lp_max_abs_sweep: U128,
pub used: [u64; 64],
pub num_used_accounts: u16,
pub next_account_id: u64,
pub free_head: u16,
pub next_free: [u16; 4096],
pub accounts: [Account; 4096],
}Expand description
Main risk engine state - fixed slab with bitmap
Fields§
§vault: U128Total vault balance (all deposited funds)
insurance_fund: InsuranceFundInsurance fund
params: RiskParamsRisk parameters
current_slot: u64Current slot (for warmup calculations)
funding_index_qpb_e6: I128Global funding index (quote per 1 base, scaled by 1e6)
last_funding_slot: u64Last slot when funding was accrued
funding_rate_bps_per_slot_last: i64Funding rate (bps per slot) in effect starting at last_funding_slot. This is the rate used for the interval [last_funding_slot, next_accrual). Anti-retroactivity: state changes at slot t can only affect funding for slots >= t.
last_crank_slot: u64Last slot when keeper crank was executed
max_crank_staleness_slots: u64Maximum allowed staleness before crank is required (in slots)
total_open_interest: U128Total open interest = sum of abs(position_size) across all accounts This measures total risk exposure in the system.
c_tot: U128Sum of all account capital: C_tot = Σ C_i Maintained incrementally via set_capital() helper.
pnl_pos_tot: U128Sum of all positive PnL: PNL_pos_tot = Σ max(PNL_i, 0) Maintained incrementally via set_pnl() helper.
liq_cursor: u16Cursor for liquidation scan (wraps around MAX_ACCOUNTS)
gc_cursor: u16Cursor for garbage collection scan (wraps around MAX_ACCOUNTS)
last_full_sweep_start_slot: u64Slot when the current full sweep started (step 0 was executed)
last_full_sweep_completed_slot: u64Slot when the last full sweep completed
crank_cursor: u16Cursor: index where the next crank will start scanning
sweep_start_idx: u16Index where the current sweep started (for completion detection)
lifetime_liquidations: u64Total number of liquidations performed (lifetime)
lifetime_force_realize_closes: u64Total number of force-realize closes performed (lifetime)
net_lp_pos: I128Net LP position: sum of position_size across all LP accounts Updated incrementally in execute_trade and close paths
lp_sum_abs: U128Sum of abs(position_size) across all LP accounts Updated incrementally in execute_trade and close paths
lp_max_abs: U128Max abs(position_size) across all LP accounts (monotone upper bound) Only increases; reset via bounded sweep at sweep completion
lp_max_abs_sweep: U128In-progress max abs for current sweep (reset at sweep start, committed at completion)
used: [u64; 64]Occupancy bitmap (4096 bits = 64 u64 words)
num_used_accounts: u16Number of used accounts (O(1) counter, fixes H2: fee bypass TOCTOU)
next_account_id: u64Next account ID to assign (monotonically increasing, never recycled)
free_head: u16Freelist head (u16::MAX = none)
next_free: [u16; 4096]Freelist next pointers
accounts: [Account; 4096]Account slab (4096 accounts)
Implementations§
Source§impl RiskEngine
impl RiskEngine
Sourcepub fn new(params: RiskParams) -> Self
pub fn new(params: RiskParams) -> Self
Create a new risk engine (stack-allocates the full struct - avoid in BPF!)
WARNING: This allocates ~6MB on the stack at MAX_ACCOUNTS=4096.
For Solana BPF programs, use init_in_place instead.
Sourcepub fn init_in_place(&mut self, params: RiskParams)
pub fn init_in_place(&mut self, params: RiskParams)
Initialize a RiskEngine in place (zero-copy friendly).
PREREQUISITE: The memory backing self MUST be zeroed before calling.
This method only sets non-zero fields to avoid touching the entire ~6MB struct.
This is the correct way to initialize RiskEngine in Solana BPF programs where stack space is limited to 4KB.
pub fn is_used(&self, idx: usize) -> bool
Sourcepub fn set_pnl(&mut self, idx: usize, new_pnl: i128)
pub fn set_pnl(&mut self, idx: usize, new_pnl: i128)
Mandatory helper: set account PnL and maintain pnl_pos_tot aggregate (spec §4.2). All code paths that modify PnL MUST call this.
Sourcepub fn set_capital(&mut self, idx: usize, new_capital: u128)
pub fn set_capital(&mut self, idx: usize, new_capital: u128)
Helper: set account capital and maintain c_tot aggregate (spec §4.1).
Sourcepub fn recompute_aggregates(&mut self)
pub fn recompute_aggregates(&mut self)
Recompute c_tot and pnl_pos_tot from account data. For test use after direct state mutation.
Sourcepub fn haircut_ratio(&self) -> (u128, u128)
pub fn haircut_ratio(&self) -> (u128, u128)
Compute haircut ratio (h_num, h_den) per spec §3.2. h = min(Residual, PNL_pos_tot) / PNL_pos_tot where Residual = max(0, V - C_tot - I). Returns (1, 1) when PNL_pos_tot == 0.
Sourcepub fn effective_pos_pnl(&self, pnl: i128) -> u128
pub fn effective_pos_pnl(&self, pnl: i128) -> u128
Compute effective positive PnL after haircut for a given account PnL (spec §3.3). PNL_eff_pos_i = floor(max(PNL_i, 0) * h_num / h_den)
Sourcepub fn effective_equity(&self, account: &Account) -> u128
pub fn effective_equity(&self, account: &Account) -> u128
Compute effective realized equity per spec §3.3. Eq_real_i = max(0, C_i + min(PNL_i, 0) + PNL_eff_pos_i)
Sourcepub fn add_lp(
&mut self,
matching_engine_program: [u8; 32],
matching_engine_context: [u8; 32],
fee_payment: u128,
) -> Result<u16>
pub fn add_lp( &mut self, matching_engine_program: [u8; 32], matching_engine_context: [u8; 32], fee_payment: u128, ) -> Result<u16>
Add a new LP account
Sourcepub fn settle_maintenance_fee(
&mut self,
idx: u16,
now_slot: u64,
oracle_price: u64,
) -> Result<u128>
pub fn settle_maintenance_fee( &mut self, idx: u16, now_slot: u64, oracle_price: u64, ) -> Result<u128>
Settle maintenance fees for an account.
Returns the fee amount due (for keeper rebate calculation).
Algorithm:
- Compute dt = now_slot - account.last_fee_slot
- If dt == 0, return 0 (no-op)
- Compute due = fee_per_slot * dt
- Deduct from fee_credits; if negative, pay from capital to insurance
- If position exists and below maintenance after fee, return Err
Sourcepub fn set_owner(&mut self, idx: u16, owner: [u8; 32]) -> Result<()>
pub fn set_owner(&mut self, idx: u16, owner: [u8; 32]) -> Result<()>
Set owner pubkey for an account
Sourcepub fn deposit_fee_credits(
&mut self,
idx: u16,
amount: u128,
now_slot: u64,
) -> Result<()>
pub fn deposit_fee_credits( &mut self, idx: u16, amount: u128, now_slot: u64, ) -> Result<()>
Pre-fund fee credits for an account.
The wrapper must have already transferred amount tokens into the vault.
This pre-pays future maintenance fees: vault increases, insurance receives
the amount as revenue (since credits are a coupon — spending them later
does NOT re-book into insurance), and the account’s fee_credits balance
increases by amount.
Sourcepub fn set_risk_reduction_threshold(&mut self, new_threshold: u128)
pub fn set_risk_reduction_threshold(&mut self, new_threshold: u128)
Set the risk reduction threshold (admin function). This controls when risk-reduction-only mode is triggered.
Sourcepub fn risk_reduction_threshold(&self) -> u128
pub fn risk_reduction_threshold(&self) -> u128
Get the current risk reduction threshold.
Sourcepub fn close_account(
&mut self,
idx: u16,
now_slot: u64,
oracle_price: u64,
) -> Result<u128>
pub fn close_account( &mut self, idx: u16, now_slot: u64, oracle_price: u64, ) -> Result<u128>
Close an account and return its capital to the caller.
Requirements:
- Account must exist
- Position must be zero (no open positions)
- fee_credits >= 0 (no outstanding fees owed)
- pnl must be 0 after settlement (positive pnl must be warmed up first)
Returns Err(PnlNotWarmedUp) if pnl > 0 (user must wait for warmup). Returns Err(Undercollateralized) if pnl < 0 (shouldn’t happen after settlement). Returns the capital amount on success.
Sourcepub fn garbage_collect_dust(&mut self) -> u32
pub fn garbage_collect_dust(&mut self) -> u32
Garbage collect dust accounts.
A “dust account” is a slot that can never pay out anything:
- position_size == 0
- capital == 0
- reserved_pnl == 0
- pnl <= 0
Any remaining negative PnL is socialized via ADL waterfall before freeing. No token transfers occur - this is purely internal bookkeeping cleanup.
Called at end of keeper_crank after liquidation/settlement has already run.
Returns the number of accounts closed.
Sourcepub fn require_fresh_crank(&self, now_slot: u64) -> Result<()>
pub fn require_fresh_crank(&self, now_slot: u64) -> Result<()>
Check if a fresh crank is required before state-changing operations. Returns Err if the crank is stale (too old).
Sourcepub fn require_recent_full_sweep(&self, now_slot: u64) -> Result<()>
pub fn require_recent_full_sweep(&self, now_slot: u64) -> Result<()>
Check if a full sweep started recently. For risk-increasing ops, we require a sweep to have STARTED recently. The priority-liquidation phase runs every crank, so once a sweep starts, the worst accounts are immediately addressed.
Sourcepub fn keeper_crank(
&mut self,
caller_idx: u16,
now_slot: u64,
oracle_price: u64,
funding_rate_bps_per_slot: i64,
allow_panic: bool,
) -> Result<CrankOutcome>
pub fn keeper_crank( &mut self, caller_idx: u16, now_slot: u64, oracle_price: u64, funding_rate_bps_per_slot: i64, allow_panic: bool, ) -> Result<CrankOutcome>
Keeper crank entrypoint - advances global state and performs maintenance.
Returns CrankOutcome with flags indicating what happened.
Behavior:
- Accrue funding
- Advance last_crank_slot if now_slot > last_crank_slot
- Settle maintenance fees for caller (50% discount)
- Process up to ACCOUNTS_PER_CRANK occupied accounts:
- Liquidation (if not in force-realize mode)
- Force-realize (if insurance at/below threshold)
- Socialization (haircut profits to cover losses)
- LP max tracking
- Detect and finalize full sweep completion
This is the single permissionless “do-the-right-thing” entrypoint.
- Always attempts caller’s maintenance settle with 50% discount (best-effort)
- Only advances last_crank_slot when now_slot > last_crank_slot
- Returns last_cursor: the index where this crank stopped
- Returns sweep_complete: true if this crank completed a full sweep
When the system has fewer than ACCOUNTS_PER_CRANK accounts, one crank covers all accounts and completes a full sweep.
Sourcepub fn mark_pnl_for_position(pos: i128, entry: u64, oracle: u64) -> Result<i128>
pub fn mark_pnl_for_position(pos: i128, entry: u64, oracle: u64) -> Result<i128>
Compute mark PnL for a position at oracle price (pure helper, no side effects). Returns the PnL from closing the position at oracle price.
- Longs: profit when oracle > entry
- Shorts: profit when entry > oracle
Sourcepub fn compute_liquidation_close_amount(
&self,
account: &Account,
oracle_price: u64,
) -> (u128, bool)
pub fn compute_liquidation_close_amount( &self, account: &Account, oracle_price: u64, ) -> (u128, bool)
Compute how much position to close for liquidation (closed-form, single-pass).
Returns (close_abs, is_full_close) where:
- close_abs = absolute position size to close
- is_full_close = true if this is a full position close (including dust kill-switch)
§Algorithm:
- Compute target_bps = maintenance_margin_bps + liquidation_buffer_bps
- Compute max safe remaining position: abs_pos_safe_max = floor(E_mtm * 10_000 * 1_000_000 / (P * target_bps))
- close_abs = abs_pos - abs_pos_safe_max
- If remaining position < min_liquidation_abs, do full close (dust kill-switch)
Uses MTM equity (capital + realized_pnl + mark_pnl) for correct risk calculation. This is deterministic, requires no iteration, and guarantees single-pass liquidation.
Sourcepub fn liquidate_at_oracle(
&mut self,
idx: u16,
now_slot: u64,
oracle_price: u64,
) -> Result<bool>
pub fn liquidate_at_oracle( &mut self, idx: u16, now_slot: u64, oracle_price: u64, ) -> Result<bool>
Liquidate a single account at oracle price if below maintenance margin.
Returns Ok(true) if liquidation occurred, Ok(false) if not needed/possible. Per spec: close position, settle losses, write off unpayable PnL, charge fee. No ADL — haircut ratio h reflects any undercollateralization.
Sourcepub fn withdrawable_pnl(&self, account: &Account) -> u128
pub fn withdrawable_pnl(&self, account: &Account) -> u128
Calculate withdrawable PNL for an account after warmup
Sourcepub fn update_warmup_slope(&mut self, idx: u16) -> Result<()>
pub fn update_warmup_slope(&mut self, idx: u16) -> Result<()>
Update warmup slope for an account NOTE: No warmup rate cap (removed for simplicity)
Sourcepub fn accrue_funding(&mut self, now_slot: u64, oracle_price: u64) -> Result<()>
pub fn accrue_funding(&mut self, now_slot: u64, oracle_price: u64) -> Result<()>
Accrue funding globally in O(1) using the stored rate (anti-retroactivity).
This uses funding_rate_bps_per_slot_last - the rate in effect since last_funding_slot.
The rate for the NEXT interval is set separately via set_funding_rate_for_next_interval.
Anti-retroactivity guarantee: state changes at slot t can only affect funding for slots >= t.
Sourcepub fn set_funding_rate_for_next_interval(&mut self, new_rate_bps_per_slot: i64)
pub fn set_funding_rate_for_next_interval(&mut self, new_rate_bps_per_slot: i64)
Set the funding rate for the NEXT interval (anti-retroactivity).
MUST be called AFTER accrue_funding() to ensure the old rate is applied to
the elapsed interval before storing the new rate.
This implements the “rate-change rule” from the spec: state changes at slot t can only affect funding for slots >= t.
Sourcepub fn accrue_funding_with_rate(
&mut self,
now_slot: u64,
oracle_price: u64,
funding_rate_bps_per_slot: i64,
) -> Result<()>
pub fn accrue_funding_with_rate( &mut self, now_slot: u64, oracle_price: u64, funding_rate_bps_per_slot: i64, ) -> Result<()>
Convenience: Set rate then accrue in one call.
This sets the rate for the interval being accrued, then accrues.
For proper anti-retroactivity in production, the rate should be set at the
START of an interval via set_funding_rate_for_next_interval, then accrued later.
Sourcepub fn touch_account(&mut self, idx: u16) -> Result<()>
pub fn touch_account(&mut self, idx: u16) -> Result<()>
Touch an account (settle funding before operations)
Sourcepub fn settle_mark_to_oracle(
&mut self,
idx: u16,
oracle_price: u64,
) -> Result<()>
pub fn settle_mark_to_oracle( &mut self, idx: u16, oracle_price: u64, ) -> Result<()>
Settle mark-to-market PnL to the current oracle price (variation margin).
This realizes all unrealized PnL at the given oracle price and resets entry_price = oracle_price. After calling this, mark_pnl_for_position will return 0 for this account at this oracle price.
This makes positions fungible: any LP can close any user’s position because PnL is settled to a common reference price.
Sourcepub fn touch_account_full(
&mut self,
idx: u16,
now_slot: u64,
oracle_price: u64,
) -> Result<()>
pub fn touch_account_full( &mut self, idx: u16, now_slot: u64, oracle_price: u64, ) -> Result<()>
Full account touch: funding + mark settlement + maintenance fees + warmup. This is the standard “lazy settlement” path called on every user operation. Triggers liquidation check if fees push account below maintenance margin.
Sourcepub fn deposit(&mut self, idx: u16, amount: u128, now_slot: u64) -> Result<()>
pub fn deposit(&mut self, idx: u16, amount: u128, now_slot: u64) -> Result<()>
Deposit funds to account.
Settles any accrued maintenance fees from the deposit first, with the remainder added to capital. This ensures fee conservation (fees are never forgiven) and prevents stuck accounts.
Sourcepub fn withdraw(
&mut self,
idx: u16,
amount: u128,
now_slot: u64,
oracle_price: u64,
) -> Result<()>
pub fn withdraw( &mut self, idx: u16, amount: u128, now_slot: u64, oracle_price: u64, ) -> Result<()>
Withdraw capital from an account. Relies on Solana transaction atomicity: if this returns Err, the entire TX aborts.
Sourcepub fn account_equity(&self, account: &Account) -> u128
pub fn account_equity(&self, account: &Account) -> u128
Realized-only equity: max(0, capital + realized_pnl).
DEPRECATED for margin checks: Use account_equity_mtm_at_oracle instead. This helper is retained for reporting, PnL display, and test assertions that specifically need realized-only equity.
Sourcepub fn account_equity_mtm_at_oracle(
&self,
account: &Account,
oracle_price: u64,
) -> u128
pub fn account_equity_mtm_at_oracle( &self, account: &Account, oracle_price: u64, ) -> u128
Mark-to-market equity at oracle price with haircut (the ONLY correct equity for margin checks). equity_mtm = max(0, C_i + min(PNL_i, 0) + PNL_eff_pos_i + mark_pnl) where PNL_eff_pos_i = floor(max(PNL_i, 0) * h_num / h_den) per spec §3.3.
FAIL-SAFE: On overflow, returns 0 (worst-case equity) to ensure liquidation can still trigger. This prevents overflow from blocking liquidation.
Sourcepub fn is_above_margin_bps_mtm(
&self,
account: &Account,
oracle_price: u64,
bps: u64,
) -> bool
pub fn is_above_margin_bps_mtm( &self, account: &Account, oracle_price: u64, bps: u64, ) -> bool
MTM margin check: is equity_mtm > required margin? This is the ONLY correct margin predicate for all risk checks.
FAIL-SAFE: Returns false on any error (treat as below margin / liquidatable).
Sourcepub fn is_above_maintenance_margin_mtm(
&self,
account: &Account,
oracle_price: u64,
) -> bool
pub fn is_above_maintenance_margin_mtm( &self, account: &Account, oracle_price: u64, ) -> bool
MTM maintenance margin check (fail-safe: returns false on overflow)
Sourcepub fn execute_trade<M: MatchingEngine>(
&mut self,
matcher: &M,
lp_idx: u16,
user_idx: u16,
now_slot: u64,
oracle_price: u64,
size: i128,
) -> Result<()>
pub fn execute_trade<M: MatchingEngine>( &mut self, matcher: &M, lp_idx: u16, user_idx: u16, now_slot: u64, oracle_price: u64, size: i128, ) -> Result<()>
Risk-reduction-only mode is entered when the system is in deficit. Warmups are frozen so pending PNL cannot become principal. Withdrawals of principal (capital) are allowed (subject to margin). Risk-increasing actions are blocked; only risk-reducing/neutral operations are allowed. Execute a trade between LP and user. Relies on Solana transaction atomicity: if this returns Err, the entire TX aborts.
Sourcepub fn settle_loss_only(&mut self, idx: u16) -> Result<()>
pub fn settle_loss_only(&mut self, idx: u16) -> Result<()>
Settle loss only (§6.1): negative PnL pays from capital immediately. If PnL still negative after capital exhausted, write off via set_pnl(i, 0). Used in two-pass settlement to ensure all losses are realized (increasing Residual) before any profit conversions use the haircut ratio.
Sourcepub fn settle_warmup_to_capital(&mut self, idx: u16) -> Result<()>
pub fn settle_warmup_to_capital(&mut self, idx: u16) -> Result<()>
Settle warmup: loss settlement + profit conversion per spec §6
§6.1 Loss settlement: negative PnL pays from capital immediately. If PnL still negative after capital exhausted, write off via set_pnl(i, 0).
§6.2 Profit conversion: warmable gross profit converts to capital at haircut ratio h. y = floor(x * h_num / h_den), where (h_num, h_den) is computed pre-conversion.
Sourcepub fn top_up_insurance_fund(&mut self, amount: u128) -> Result<bool>
pub fn top_up_insurance_fund(&mut self, amount: u128) -> Result<bool>
Top up insurance fund
Adds tokens to both vault and insurance fund. Returns true if the top-up brings insurance above the risk reduction threshold.
Sourcepub fn check_conservation(&self, oracle_price: u64) -> bool
pub fn check_conservation(&self, oracle_price: u64) -> bool
Check conservation invariant (spec §3.1)
Primary invariant: V >= C_tot + I
Extended check: vault >= sum(capital) + sum(positive_pnl_clamped) + insurance with bounded rounding slack from funding/mark settlement.
We also verify the full accounting identity including settled/unsettled PnL: vault >= sum(capital) + sum(settled_pnl + mark_pnl) + insurance The difference (slack) must be bounded by MAX_ROUNDING_SLACK.
Sourcepub fn advance_slot(&mut self, slots: u64)
pub fn advance_slot(&mut self, slots: u64)
Advance to next slot (for testing warmup)
Trait Implementations§
Source§impl Clone for RiskEngine
impl Clone for RiskEngine
Source§fn clone(&self) -> RiskEngine
fn clone(&self) -> RiskEngine
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for RiskEngine
impl Debug for RiskEngine
Source§impl PartialEq for RiskEngine
impl PartialEq for RiskEngine
impl Eq for RiskEngine
impl StructuralPartialEq for RiskEngine
Auto Trait Implementations§
impl Freeze for RiskEngine
impl RefUnwindSafe for RiskEngine
impl Send for RiskEngine
impl Sync for RiskEngine
impl Unpin for RiskEngine
impl UnwindSafe for RiskEngine
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more