Skip to content

Commit 513a1a5

Browse files
authored
ctutils: add CtFind trait (#1361)
Adds a trait with a similar shape to the `Iterator::find` method, which returns the first item in a collection that matches a given predicate. The trait is impl'd for `[T; N] and `[T]` where `T: CtSelect + Default`. This also adds a helper method used in the implementation `CtOption::insert_if` which is made public, and `CtOption::insert` which mirrors `Option::insert` for consistency.
1 parent 1658a0f commit 513a1a5

File tree

4 files changed

+135
-1
lines changed

4 files changed

+135
-1
lines changed

ctutils/src/ct_option.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ impl<T> CtOption<T> {
150150
self.as_inner_unchecked()
151151
}
152152

153+
/// Inserts `value` into the [`CtOption`], then returns a mutable reference to it.
154+
///
155+
/// If the option already contains a value, the old value is dropped.
156+
pub fn insert(&mut self, value: T) -> &mut T {
157+
self.value = value;
158+
self.is_some = Choice::TRUE;
159+
&mut self.value
160+
}
161+
162+
/// Conditionally inserts `value` into the [`CtOption`] if the given condition holds.
163+
pub fn insert_if(&mut self, value: &T, condition: Choice)
164+
where
165+
T: CtSelect,
166+
{
167+
self.value.ct_assign(value, condition);
168+
self.is_some.ct_assign(&Choice::TRUE, condition);
169+
}
170+
153171
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
154172
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
155173
///
@@ -735,6 +753,28 @@ mod tests {
735753
assert_eq!(SOME.filter_by(Choice::TRUE).unwrap(), VALUE);
736754
}
737755

756+
#[test]
757+
fn insert() {
758+
let mut example = NONE;
759+
assert!(example.is_none().to_bool());
760+
761+
let ret = example.insert(42);
762+
assert_eq!(ret, &42);
763+
assert!(example.is_some().to_bool());
764+
}
765+
766+
#[test]
767+
fn insert_if() {
768+
let mut example = NONE;
769+
assert!(example.is_none().to_bool());
770+
771+
example.insert_if(&42, Choice::FALSE);
772+
assert!(example.is_none().to_bool());
773+
774+
example.insert_if(&42, Choice::TRUE);
775+
assert_eq!(example.unwrap(), 42);
776+
}
777+
738778
#[test]
739779
fn map() {
740780
assert!(NONE.map(|value| value + 1).ct_eq(&NONE).to_bool());

ctutils/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,6 @@ mod traits;
9494
pub use bytes::{BytesCtEq, BytesCtSelect};
9595
pub use choice::Choice;
9696
pub use ct_option::CtOption;
97-
pub use traits::{ct_eq::CtEq, ct_gt::CtGt, ct_lt::CtLt, ct_neg::CtNeg, ct_select::CtSelect};
97+
pub use traits::{
98+
ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lt::CtLt, ct_neg::CtNeg, ct_select::CtSelect,
99+
};

ctutils/src/traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! on in the same module.
55
66
pub(crate) mod ct_eq;
7+
pub(crate) mod ct_find;
78
pub(crate) mod ct_gt;
89
pub(crate) mod ct_lt;
910
pub(crate) mod ct_neg;

ctutils/src/traits/ct_find.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use crate::{Choice, CtOption, CtSelect};
2+
3+
#[cfg(doc)]
4+
use core::iter::Iterator;
5+
6+
/// Constant-time equivalent of [`Iterator::find`], which can search a collection by iterating over
7+
/// every element and applying the given predicate to each item, then selecting the first matching
8+
/// entry.
9+
pub trait CtFind<T: CtSelect> {
10+
/// Iterate through every `T` item in `&self`, applying the given `predicate` which can select
11+
/// a specific item by returning [`Choice::TRUE`].
12+
///
13+
/// The first item where `predicate` returns [`Choice::TRUE`] is selected, or the [`CtOption`]
14+
/// equivalent of `None` is returned if the `predicate` returns [`Choice::FALSE`] for all items.
15+
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
16+
where
17+
P: Fn(&T) -> Choice;
18+
}
19+
20+
impl<T> CtFind<T> for [T]
21+
where
22+
T: CtSelect + Default,
23+
{
24+
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
25+
where
26+
P: Fn(&T) -> Choice,
27+
{
28+
let mut ret = CtOption::none();
29+
30+
for item in self {
31+
ret.insert_if(item, predicate(item) & ret.is_none());
32+
}
33+
34+
ret
35+
}
36+
}
37+
38+
impl<T, const N: usize> CtFind<T> for [T; N]
39+
where
40+
T: CtSelect + Default,
41+
{
42+
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
43+
where
44+
P: Fn(&T) -> Choice,
45+
{
46+
self.as_slice().ct_find(predicate)
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::CtFind;
53+
54+
mod array {
55+
use super::*;
56+
use crate::{CtEq, CtGt};
57+
58+
const ARRAY: [u8; 6] = [0, 0, 0, 1, 2, 3];
59+
60+
#[test]
61+
fn ct_find() {
62+
// Find the first nonzero even number
63+
assert_eq!(
64+
ARRAY.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
65+
2
66+
);
67+
68+
// Predicate where nothing matches
69+
assert!(ARRAY.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
70+
}
71+
}
72+
73+
mod slice {
74+
use super::*;
75+
use crate::{CtEq, CtGt};
76+
77+
const SLICE: &[u8] = &[0, 0, 0, 1, 2, 3];
78+
79+
#[test]
80+
fn ct_find() {
81+
// Find the first nonzero even number
82+
assert_eq!(
83+
SLICE.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
84+
2
85+
);
86+
87+
// Predicate where nothing matches
88+
assert!(SLICE.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)