diff --git a/.gitignore b/.gitignore index bae4be6..b967cf8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,7 @@ erl_crash.dump *.ez # Ignore the output of benchmarking -/bench/results \ No newline at end of file +/bench/results + +# IDEA specific +native/sorted_set_nif/.idea diff --git a/native/sorted_set_nif/Cargo.lock b/native/sorted_set_nif/Cargo.lock index 7d46496..45af955 100644 --- a/native/sorted_set_nif/Cargo.lock +++ b/native/sorted_set_nif/Cargo.lock @@ -1,3 +1,41 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "erlang_nif-sys" version = "0.6.4" @@ -6,6 +44,26 @@ dependencies = [ "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heck" version = "0.3.0" @@ -22,11 +80,37 @@ dependencies = [ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libc" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustler" version = "0.18.0" @@ -50,6 +134,7 @@ dependencies = [ name = "sorted_set_nif" version = "0.1.0" dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustler 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustler_codegen 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -65,6 +150,16 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "0.15.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synom" version = "0.11.3" @@ -73,6 +168,17 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.2.1" @@ -83,6 +189,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "0.1.1" @@ -102,16 +213,30 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum backtrace 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "f92d5d536fa03dc3d93711d97bac1fae2eb59aba467ca4c6600c0119da614f51" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum erlang_nif-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9bc72dbc2c220693a2a009ec97705c144ea5cc5db703df43feb903663c999536" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" +"checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" "checksum rustler 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56c8f41f12f189bea1d04f188834f62ab58da6126f76edb67d2ef90070c469b2" "checksum rustler_codegen 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf38a981e9ae5f9d7b326e4ebadcf51fb439a88173de75671ee8b97c8e5eb713" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/native/sorted_set_nif/Cargo.toml b/native/sorted_set_nif/Cargo.toml index 5ba75d1..3fe70bd 100644 --- a/native/sorted_set_nif/Cargo.toml +++ b/native/sorted_set_nif/Cargo.toml @@ -12,3 +12,9 @@ crate-type = ["cdylib"] rustler = "0.18.0" rustler_codegen = "0.18.0" lazy_static = "1.0" +failure = "0.1.5" + + +[profile.release] +lto = true +codegen-units = 1 \ No newline at end of file diff --git a/native/sorted_set_nif/src/bucket.rs b/native/sorted_set_nif/src/bucket.rs index 5440026..86a2574 100644 --- a/native/sorted_set_nif/src/bucket.rs +++ b/native/sorted_set_nif/src/bucket.rs @@ -1,10 +1,9 @@ use std::cmp::Ordering; use std::ptr; use supported_term::SupportedTerm; -use AddResult; -use AddResult::{Added, Duplicate}; +use Error; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] pub struct Bucket { pub data: Vec, } @@ -14,12 +13,12 @@ impl Bucket { self.data.len() } - pub fn add(&mut self, item: SupportedTerm) -> AddResult { + pub fn add(&mut self, item: SupportedTerm) -> Result { match self.data.binary_search(&item) { - Ok(idx) => Duplicate(idx), + Ok(idx) => Err(Error::Duplicate(idx)), Err(idx) => { self.data.insert(idx, item); - Added(idx) + Ok(idx) } } } @@ -36,11 +35,7 @@ impl Bucket { self.data.set_len(at); other.set_len(other_len); - ptr::copy_nonoverlapping( - self.data.as_ptr().offset(at as isize), - other.as_mut_ptr(), - other.len(), - ); + ptr::copy_nonoverlapping(self.data.as_ptr().add(at), other.as_mut_ptr(), other.len()); } Bucket { data: other } @@ -72,11 +67,10 @@ mod tests { use bucket::Bucket; use std::cmp::Ordering; use supported_term::SupportedTerm; - use AddResult; #[test] fn test_item_compare_empty_bucket() { - let bucket = Bucket { data: Vec::new() }; + let bucket = Bucket::default(); let item = SupportedTerm::Integer(5); @@ -85,9 +79,9 @@ mod tests { #[test] fn test_item_compare_when_less_than_first_item() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); let first_item = SupportedTerm::Integer(5); - assert_eq!(bucket.add(first_item), AddResult::Added(0)); + assert_eq!(bucket.add(first_item).unwrap(), 0); let item = SupportedTerm::Integer(3); @@ -96,21 +90,21 @@ mod tests { #[test] fn test_item_compare_when_equal_to_first_item() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); let first_item = SupportedTerm::Integer(5); let item = first_item.clone(); - assert_eq!(bucket.add(first_item), AddResult::Added(0)); + assert_eq!(bucket.add(first_item).unwrap(), 0); assert_eq!(bucket.item_compare(&item), Ordering::Equal); } #[test] fn test_item_compare_when_greater_than_last_item() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); - assert_eq!(bucket.add(SupportedTerm::Integer(1)), AddResult::Added(0)); - assert_eq!(bucket.add(SupportedTerm::Integer(2)), AddResult::Added(1)); - assert_eq!(bucket.add(SupportedTerm::Integer(3)), AddResult::Added(2)); + assert_eq!(bucket.add(SupportedTerm::Integer(1)).unwrap(), 0); + assert_eq!(bucket.add(SupportedTerm::Integer(2)).unwrap(), 1); + assert_eq!(bucket.add(SupportedTerm::Integer(3)).unwrap(), 2); let item = SupportedTerm::Integer(5); @@ -119,11 +113,11 @@ mod tests { #[test] fn test_item_compare_when_equal_to_last_item() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); - assert_eq!(bucket.add(SupportedTerm::Integer(1)), AddResult::Added(0)); - assert_eq!(bucket.add(SupportedTerm::Integer(2)), AddResult::Added(1)); - assert_eq!(bucket.add(SupportedTerm::Integer(3)), AddResult::Added(2)); + assert_eq!(bucket.add(SupportedTerm::Integer(1)).unwrap(), 0); + assert_eq!(bucket.add(SupportedTerm::Integer(2)).unwrap(), 1); + assert_eq!(bucket.add(SupportedTerm::Integer(3)).unwrap(), 2); let item = SupportedTerm::Integer(3); @@ -132,11 +126,11 @@ mod tests { #[test] fn test_item_between_first_and_last_duplicate() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); - assert_eq!(bucket.add(SupportedTerm::Integer(1)), AddResult::Added(0)); - assert_eq!(bucket.add(SupportedTerm::Integer(2)), AddResult::Added(1)); - assert_eq!(bucket.add(SupportedTerm::Integer(3)), AddResult::Added(2)); + assert_eq!(bucket.add(SupportedTerm::Integer(1)).unwrap(), 0); + assert_eq!(bucket.add(SupportedTerm::Integer(2)).unwrap(), 1); + assert_eq!(bucket.add(SupportedTerm::Integer(3)).unwrap(), 2); let item = SupportedTerm::Integer(1); @@ -145,11 +139,11 @@ mod tests { #[test] fn test_item_between_first_and_last_unique() { - let mut bucket = Bucket { data: Vec::new() }; + let mut bucket = Bucket::default(); - assert_eq!(bucket.add(SupportedTerm::Integer(2)), AddResult::Added(0)); - assert_eq!(bucket.add(SupportedTerm::Integer(4)), AddResult::Added(1)); - assert_eq!(bucket.add(SupportedTerm::Integer(6)), AddResult::Added(2)); + assert_eq!(bucket.add(SupportedTerm::Integer(2)).unwrap(), 0); + assert_eq!(bucket.add(SupportedTerm::Integer(4)).unwrap(), 1); + assert_eq!(bucket.add(SupportedTerm::Integer(6)).unwrap(), 2); let item = SupportedTerm::Integer(3); diff --git a/native/sorted_set_nif/src/configuration.rs b/native/sorted_set_nif/src/configuration.rs index 55a9ecd..1985445 100644 --- a/native/sorted_set_nif/src/configuration.rs +++ b/native/sorted_set_nif/src/configuration.rs @@ -1,10 +1,12 @@ +use std::num::NonZeroUsize; + #[derive(Debug)] pub struct Configuration { /// Internally we maintain buckets to reduce the cost of inserts. This configures /// how large a bucket can grow to before it is forced to be split. /// /// Default: 200 - pub max_bucket_size: usize, + pub max_bucket_size: NonZeroUsize, /// Similarly to a bucket, the SortedSet maintains a Vec of buckets. This lets you /// preallocate to avoid resizing the Vector if you can anticipate the size. @@ -15,9 +17,22 @@ pub struct Configuration { impl Default for Configuration { fn default() -> Self { - return Self { - max_bucket_size: 200, - initial_set_capacity: 0, - }; + Self::new(200, 0) + } +} + +impl Configuration { + pub fn new(max_bucket_size: usize, initial_set_capacity: usize) -> Self { + Self { + max_bucket_size: NonZeroUsize::new(max_bucket_size) + .expect("max_bucket_size must be greater than 0"), + initial_set_capacity, + } + } + + // Currently used only in tests + #[allow(dead_code)] + pub fn with_max_bucket_size(max_bucket_size: usize) -> Self { + Self::new(max_bucket_size, 0) } } diff --git a/native/sorted_set_nif/src/lib.rs b/native/sorted_set_nif/src/lib.rs index 5183de6..e93b5e8 100644 --- a/native/sorted_set_nif/src/lib.rs +++ b/native/sorted_set_nif/src/lib.rs @@ -2,6 +2,8 @@ extern crate rustler; #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate failure; mod bucket; mod configuration; @@ -41,32 +43,22 @@ mod atoms { pub struct SortedSetResource(Mutex); -#[derive(Debug, PartialEq)] -pub enum AddResult { - Added(usize), +#[derive(Debug, PartialEq, Eq, Fail)] +pub enum Error { + #[fail(display = "Duplicate at index: {}", 0)] Duplicate(usize), -} - -#[derive(Debug, PartialEq)] -pub enum RemoveResult { - Removed(usize), - NotFound, -} - -#[derive(Debug, PartialEq)] -pub enum FindResult { - Found { - bucket_idx: usize, - inner_idx: usize, - idx: usize, - }, + #[fail(display = "Not found at index: {}", 0)] + NotFoundAtIndex(usize), + #[fail(display = "Not found")] NotFound, + #[fail(display = "Max bucket size exceeded")] + MaxBucketSizeExceeded, } -#[derive(Debug, PartialEq)] -pub enum AppendBucketResult { - Ok, - MaxBucketSizeExceeded, +pub struct FoundData { + bucket_idx: usize, + inner_idx: usize, + idx: usize, } rustler_export_nifs! { @@ -98,10 +90,7 @@ fn empty<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { let initial_set_capacity: usize = (initial_item_capacity / max_bucket_size) + 1; - let configuration = Configuration { - max_bucket_size, - initial_set_capacity, - }; + let configuration = Configuration::new(max_bucket_size, initial_set_capacity); let resource = ResourceArc::new(SortedSetResource(Mutex::new(SortedSet::empty( configuration, @@ -116,10 +105,7 @@ fn new<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { let initial_set_capacity: usize = (initial_item_capacity / max_bucket_size) + 1; - let configuration = Configuration { - max_bucket_size, - initial_set_capacity, - }; + let configuration = Configuration::new(max_bucket_size, initial_set_capacity); let resource = ResourceArc::new(SortedSetResource(Mutex::new(SortedSet::new(configuration)))); @@ -143,10 +129,11 @@ fn append_bucket<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { }; match set.append_bucket(items) { - AppendBucketResult::Ok => Ok(atoms::ok().encode(env)), - AppendBucketResult::MaxBucketSizeExceeded => { + Ok(_) => Ok(atoms::ok().encode(env)), + Err(Error::MaxBucketSizeExceeded) => { Ok((atoms::error(), atoms::max_bucket_size_exceeded()).encode(env)) } + _ => unreachable!(), } } @@ -167,8 +154,9 @@ fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { }; match set.add(item) { - AddResult::Added(idx) => Ok((atoms::ok(), atoms::added(), idx).encode(env)), - AddResult::Duplicate(idx) => Ok((atoms::ok(), atoms::duplicate(), idx).encode(env)), + Ok(idx) => Ok((atoms::ok(), atoms::added(), idx).encode(env)), + Err(Error::Duplicate(idx)) => Ok((atoms::ok(), atoms::duplicate(), idx).encode(env)), + _ => unreachable!(), } } @@ -189,8 +177,9 @@ fn remove<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { }; match set.remove(&item) { - RemoveResult::Removed(idx) => Ok((atoms::ok(), atoms::removed(), idx).encode(env)), - RemoveResult::NotFound => Ok((atoms::error(), atoms::not_found()).encode(env)), + Ok(idx) => Ok((atoms::ok(), atoms::removed(), idx).encode(env)), + Err(Error::NotFound) => Ok((atoms::error(), atoms::not_found()).encode(env)), + _ => unreachable!(), } } @@ -274,12 +263,9 @@ fn find_index<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { }; match set.find_index(&item) { - FindResult::Found { - bucket_idx: _, - inner_idx: _, - idx, - } => Ok((atoms::ok(), idx).encode(env)), - FindResult::NotFound => Ok((atoms::error(), atoms::not_found()).encode(env)), + Ok(FoundData { idx, .. }) => Ok((atoms::ok(), idx).encode(env)), + Err(Error::NotFound) => Ok((atoms::error(), atoms::not_found()).encode(env)), + _ => unreachable!(), } } @@ -294,7 +280,7 @@ fn debug<'a>(env: Env<'a>, args: &[Term<'a>]) -> NifResult> { Ok(guard) => guard, }; - Ok((atoms::ok(), set.debug()).encode(env)) + Ok((atoms::ok(), format!("{:#?}", set)).encode(env)) } fn convert_to_supported_term(term: &Term) -> Option { diff --git a/native/sorted_set_nif/src/sorted_set.rs b/native/sorted_set_nif/src/sorted_set.rs index 0e7a63c..e2e5f3c 100644 --- a/native/sorted_set_nif/src/sorted_set.rs +++ b/native/sorted_set_nif/src/sorted_set.rs @@ -2,10 +2,8 @@ use bucket::Bucket; use configuration::Configuration; use std::cmp::min; use supported_term::SupportedTerm; -use AddResult; -use AppendBucketResult; -use FindResult; -use RemoveResult; +use Error; +use FoundData; #[derive(Debug)] pub struct SortedSet { @@ -16,34 +14,29 @@ pub struct SortedSet { impl SortedSet { pub fn empty(configuration: Configuration) -> SortedSet { - if configuration.max_bucket_size < 1 { - panic!("SortedSet max_bucket_size must be greater than 0"); - } - - let buckets = Vec::with_capacity(configuration.initial_set_capacity); + let initial_capacity = configuration.initial_set_capacity; SortedSet { configuration, - buckets, + buckets: Vec::with_capacity(initial_capacity), size: 0, } } pub fn new(configuration: Configuration) -> SortedSet { let mut result = SortedSet::empty(configuration); - result.buckets.push(Bucket { data: Vec::new() }); + result.buckets.push(Bucket::default()); result } - pub fn append_bucket(&mut self, items: Vec) -> AppendBucketResult { - if self.configuration.max_bucket_size <= items.len() { - return AppendBucketResult::MaxBucketSizeExceeded; + pub fn append_bucket(&mut self, items: Vec) -> Result<(), Error> { + if self.configuration.max_bucket_size.get() <= items.len() { + Err(Error::MaxBucketSizeExceeded) + } else { + self.size += items.len(); + self.buckets.push(Bucket { data: items }); + Ok(()) } - - self.size += items.len(); - self.buckets.push(Bucket { data: items }); - - AppendBucketResult::Ok } #[inline] @@ -57,18 +50,16 @@ impl SortedSet { } } - pub fn find_index(&self, item: &SupportedTerm) -> FindResult { + pub fn find_index(&self, item: &SupportedTerm) -> Result { let bucket_idx = self.find_bucket_index(item); match self.buckets[bucket_idx].data.binary_search(&item) { - Ok(idx) => { - return FindResult::Found { - bucket_idx, - inner_idx: idx, - idx: self.effective_index(bucket_idx, idx), - } - } - Err(_) => return FindResult::NotFound, + Ok(idx) => Ok(FoundData { + bucket_idx, + inner_idx: idx, + idx: self.effective_index(bucket_idx, idx), + }), + Err(_) => Err(Error::NotFound), } } @@ -83,36 +74,39 @@ impl SortedSet { result } - pub fn add(&mut self, item: SupportedTerm) -> AddResult { + pub fn add(&mut self, item: SupportedTerm) -> Result { let bucket_idx = self.find_bucket_index(&item); match self.buckets[bucket_idx].add(item) { - AddResult::Added(idx) => { + Ok(idx) => { let effective_idx = self.effective_index(bucket_idx, idx); let bucket_len = self.buckets[bucket_idx].len(); - if bucket_len >= self.configuration.max_bucket_size { + if bucket_len >= self.configuration.max_bucket_size.get() { let new_bucket = self.buckets[bucket_idx].split(); self.buckets.insert(bucket_idx + 1, new_bucket); } self.size += 1; - AddResult::Added(effective_idx) - } - AddResult::Duplicate(idx) => { - AddResult::Duplicate(self.effective_index(bucket_idx, idx)) + Ok(effective_idx) } + Err(error) => match error { + Error::Duplicate(idx) => { + Err(Error::Duplicate(self.effective_index(bucket_idx, idx))) + } + _ => unreachable!(), + }, } } - pub fn remove(&mut self, item: &SupportedTerm) -> RemoveResult { + pub fn remove(&mut self, item: &SupportedTerm) -> Result { match self.find_index(item) { - FindResult::Found { + Ok(FoundData { bucket_idx, inner_idx, idx, - } => { + }) => { if self.size == 0 { panic!(format!( "Just found item {:?} but size is 0, internal structure error \n @@ -132,9 +126,9 @@ impl SortedSet { self.size -= 1; - return RemoveResult::Removed(idx); + Ok(idx) } - FindResult::NotFound => RemoveResult::NotFound, + Err(e) => Err(e), } } @@ -201,7 +195,7 @@ impl SortedSet { } // Reduce the amount remaining to be satisied by the number of items in the bucket - amount = amount - items_in_bucket; + amount -= items_in_bucket; // Set index to 0, we only care to preserve the index from seeking for the bucket // that contains the first element. @@ -217,7 +211,7 @@ impl SortedSet { } pub fn to_vec(&self) -> Vec { - let mut new_vec = Vec::new(); + let mut new_vec = Vec::with_capacity(self.size()); for bucket in self.buckets.iter() { new_vec.extend(bucket.data.clone().into_iter()); } @@ -227,15 +221,11 @@ impl SortedSet { pub fn size(&self) -> usize { self.size } - - pub fn debug(&self) -> String { - format!("{:#?}", self) - } } impl Default for SortedSet { fn default() -> Self { - return Self::new(Configuration::default()); + Self::new(Configuration::default()) } } @@ -244,8 +234,7 @@ mod tests { use configuration::Configuration; use supported_term::SupportedTerm; use supported_term::SupportedTerm::{Bitstring, Integer}; - use AddResult::{Added, Duplicate}; - use RemoveResult::{NotFound, Removed}; + use Error; use SortedSet; #[test] @@ -270,26 +259,20 @@ mod tests { assert_eq!(set.size(), 0); let item = Bitstring(String::from("test-item")); - match set.add(item) { - Added(idx) => assert_eq!(idx, 0), - Duplicate(idx) => panic!(format!("Unexpected Duplicate({}) on initial add", idx)), - }; + set.add(item).map(|idx| assert_eq!(idx, 0)).unwrap(); assert_eq!(set.size(), 1); let item = Bitstring(String::from("test-item")); - match set.add(item) { - Added(idx) => panic!(format!("Unexpected Added({}) on subsequent add", idx)), - Duplicate(idx) => assert_eq!(idx, 0), - } + assert!(set + .add(item) + .map_err(|err| assert_eq!(err, Error::Duplicate(0))) + .is_err()); assert_eq!(set.size(), 1); } #[test] fn test_retrieving_an_item() { - let mut set: SortedSet = SortedSet::new(Configuration { - max_bucket_size: 3, - ..Configuration::default() - }); + let mut set: SortedSet = SortedSet::new(Configuration::with_max_bucket_size(3)); set.add(Bitstring(String::from("aaa"))); set.add(Bitstring(String::from("bbb"))); @@ -327,13 +310,7 @@ mod tests { let item = Bitstring(String::from("bbb")); - match set.remove(&item) { - Removed(idx) => assert_eq!(idx, 1), - NotFound => panic!(format!( - "Unexpected NotFound for item that should be present: {:?}", - item - )), - } + set.remove(&item).map(|idx| assert_eq!(idx, 1)).unwrap(); assert_eq!( set.to_vec(), @@ -363,13 +340,7 @@ mod tests { let item = Bitstring(String::from("zzz")); - match set.remove(&item) { - Removed(idx) => panic!( - "Unexpected Removed({}) for item that should not be present", - idx - ), - NotFound => assert!(true), - } + assert!(set.remove(&item).is_err()); assert_eq!( set.to_vec(), @@ -383,10 +354,7 @@ mod tests { #[test] fn test_removing_from_non_leading_bucket() { - let mut set: SortedSet = SortedSet::new(Configuration { - max_bucket_size: 3, - ..Configuration::default() - }); + let mut set: SortedSet = SortedSet::new(Configuration::with_max_bucket_size(3)); set.add(Bitstring(String::from("aaa"))); set.add(Bitstring(String::from("bbb"))); @@ -407,13 +375,7 @@ mod tests { let item = Bitstring(String::from("ddd")); - match set.remove(&item) { - Removed(idx) => assert_eq!(idx, 3), - NotFound => panic!(format!( - "Unexpected NotFound for item that should be present: {:?}", - item - )), - } + set.remove(&item).map(|idx| assert_eq!(idx, 3)).unwrap(); assert_eq!( set.to_vec(), @@ -428,10 +390,7 @@ mod tests { #[test] fn test_find_bucket_in_empty_set() { - let set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let set = SortedSet::new(Configuration::with_max_bucket_size(3)); assert_eq!(set.find_bucket_index(&Integer(10)), 0); } @@ -528,10 +487,7 @@ mod tests { #[test] fn test_find_bucket_when_less_than_first_item_in_set() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -542,10 +498,7 @@ mod tests { #[test] fn test_find_bucket_when_equal_to_first_item_in_set() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -556,10 +509,7 @@ mod tests { #[test] fn test_find_bucket_when_in_first_bucket_unique() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -570,10 +520,7 @@ mod tests { #[test] fn test_find_bucket_when_in_first_bucket_duplicate() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -584,10 +531,7 @@ mod tests { #[test] fn test_find_bucket_when_between_buckets_selects_the_right_hand_bucket() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -598,10 +542,7 @@ mod tests { #[test] fn test_find_bucket_when_in_interior_bucket_unique() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -612,10 +553,7 @@ mod tests { #[test] fn test_find_bucket_when_in_interior_bucket_duplicate() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -626,10 +564,7 @@ mod tests { #[test] fn test_find_bucket_when_in_last_bucket_unique() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -640,10 +575,7 @@ mod tests { #[test] fn test_find_bucket_when_in_last_bucket_duplicate() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -654,10 +586,7 @@ mod tests { #[test] fn test_find_bucket_when_equal_to_last_item_in_set() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -668,10 +597,7 @@ mod tests { #[test] fn test_find_bucket_when_greater_than_last_item_in_set() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -682,10 +608,7 @@ mod tests { #[test] fn test_slice_starting_at_0_amount_0() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -711,10 +634,7 @@ mod tests { #[test] fn test_slice_single_bucket_satisfiable() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -725,10 +645,7 @@ mod tests { #[test] fn test_slice_multi_cell_satisfiable() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -747,10 +664,7 @@ mod tests { #[test] fn test_slice_exactly_exhausted_from_non_terminal() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -771,10 +685,7 @@ mod tests { #[test] fn test_slice_over_exhausted_from_non_terminal() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -795,10 +706,7 @@ mod tests { #[test] fn test_slice_exactly_exhausted_from_terminal() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); @@ -812,10 +720,7 @@ mod tests { #[test] fn test_slice_over_exhausted_from_terminal() { - let mut set = SortedSet::new(Configuration { - max_bucket_size: 5, - ..Configuration::default() - }); + let mut set = SortedSet::new(Configuration::with_max_bucket_size(5)); for i in 1..10 { set.add(Integer(i * 2)); diff --git a/native/sorted_set_nif/src/supported_term.rs b/native/sorted_set_nif/src/supported_term.rs index aef82b4..ce8cd74 100644 --- a/native/sorted_set_nif/src/supported_term.rs +++ b/native/sorted_set_nif/src/supported_term.rs @@ -4,7 +4,6 @@ use rustler::types::tuple::make_tuple; use rustler::Encoder; use rustler::Env; use rustler::Term; -use std::cmp::min; use std::cmp::Ordering; /// SupportedTerm is an enum that covers all the Erlang / Elixir term types that can be stored in @@ -21,7 +20,7 @@ use std::cmp::Ordering; /// /// Types that are supported but not explicitly listed /// - Boolean (Note that booleans in Erlang / Elixir are just atoms) -#[derive(Eq, Debug, Clone)] +#[derive(Eq, Debug, Clone, PartialEq)] pub enum SupportedTerm { Integer(i64), Atom(String), @@ -50,12 +49,10 @@ impl Ord for SupportedTerm { let other_length = inner.len(); if self_length == other_length { - let mut idx = 0; - while idx < self_length { - match self_inner[idx].cmp(&inner[idx]) { - Ordering::Less => return Ordering::Less, - Ordering::Greater => return Ordering::Greater, - _ => idx += 1, + for (self_term, term) in self_inner.iter().zip(inner.iter()) { + match self_term.cmp(term) { + Ordering::Equal => {} + o => return o, } } Ordering::Equal @@ -69,29 +66,7 @@ impl Ord for SupportedTerm { SupportedTerm::Integer(_) => Ordering::Greater, SupportedTerm::Atom(_) => Ordering::Greater, SupportedTerm::Tuple(_) => Ordering::Greater, - SupportedTerm::List(inner) => { - let self_length = self_inner.len(); - let other_length = inner.len(); - - let max_common = min(self_length, other_length); - let mut idx = 0; - - while idx < max_common { - match self_inner[idx].cmp(&inner[idx]) { - Ordering::Greater => return Ordering::Greater, - Ordering::Less => return Ordering::Less, - _ => idx += 1, - } - } - - if self_length == other_length { - Ordering::Equal - } else if self_length < other_length { - Ordering::Less - } else { - Ordering::Greater - } - } + SupportedTerm::List(inner) => self_inner.cmp(inner), _ => Ordering::Less, }, SupportedTerm::Bitstring(self_inner) => match other { @@ -108,65 +83,6 @@ impl PartialOrd for SupportedTerm { } } -impl PartialEq for SupportedTerm { - fn eq(&self, other: &SupportedTerm) -> bool { - match self { - SupportedTerm::Integer(self_inner) => match other { - SupportedTerm::Integer(inner) => self_inner == inner, - _ => false, - }, - SupportedTerm::Atom(self_inner) => match other { - SupportedTerm::Atom(inner) => self_inner == inner, - _ => false, - }, - SupportedTerm::Tuple(self_inner) => match other { - SupportedTerm::Tuple(inner) => { - let length = self_inner.len(); - - if length != inner.len() { - return false; - } - - let mut idx = 0; - - while idx < length { - if self_inner[idx] != inner[idx] { - return false; - } - } - - true - } - _ => false, - }, - SupportedTerm::List(self_inner) => match other { - SupportedTerm::List(inner) => { - let length = self_inner.len(); - - if length != inner.len() { - return false; - } - - let mut idx = 0; - - while idx < length { - if self_inner[idx] != inner[idx] { - return false; - } - } - - true - } - _ => false, - }, - SupportedTerm::Bitstring(self_inner) => match other { - SupportedTerm::Bitstring(inner) => self_inner == inner, - _ => false, - }, - } - } -} - impl Encoder for SupportedTerm { fn encode<'a>(&self, env: Env<'a>) -> Term<'a> { match self { @@ -176,7 +92,7 @@ impl Encoder for SupportedTerm { Err(_) => atoms::error().encode(env), }, SupportedTerm::Tuple(inner) => { - let terms: Vec<_> = inner.into_iter().map(|t| t.encode(env)).collect(); + let terms: Vec<_> = inner.iter().map(|t| t.encode(env)).collect(); make_tuple(env, terms.as_ref()).encode(env) } SupportedTerm::List(inner) => inner.encode(env),