From 7d870758cbd9ad4181471ad40184d1bac1204e1e Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sat, 28 Jan 2017 20:46:29 -0800 Subject: Use 'Cow' to remove unnecessary allocations --- examples/basic/src/main.rs | 8 +- src/config.rs | 263 ++++++++++++++++++++++++++++++++------------- src/file/json.rs | 3 +- src/file/toml.rs | 3 +- src/lib.rs | 42 ++++---- src/value.rs | 86 ++++++++------- 6 files changed, 263 insertions(+), 142 deletions(-) diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index a0e40bf..6690f0e 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -2,10 +2,10 @@ extern crate config; fn main() { // Set defaults for `window.width` and `window.height` - config::set_default("window.title", "Basic"); - config::set_default("window.width", 640); - config::set_default("window.height", 480); - config::set_default("debug", true); + config::set_default("window.title", "Basic").unwrap(); + config::set_default("window.width", 640).unwrap(); + config::set_default("window.height", 480).unwrap(); + config::set_default("debug", true).unwrap(); // Note that you can retrieve the stored values as any type as long // as there exists a reasonable conversion diff --git a/src/config.rs b/src/config.rs index e5b25f6..a1a36cb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,82 +2,199 @@ use value::Value; use source::{Source, SourceBuilder}; use std::error::Error; +use std::fmt; +use std::borrow::Cow; use std::collections::HashMap; -#[derive(Default)] -pub struct Config { - defaults: HashMap, - overrides: HashMap, - sources: Vec>, +#[derive(Default, Debug)] +pub struct FrozenError { } + +impl fmt::Display for FrozenError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FrozenError") + } } -impl Config { - pub fn new() -> Config { - Default::default() +impl Error for FrozenError { + fn description(&self) -> &'static str { + "configuration is frozen" } +} - /// Merge in configuration values from the given source. - pub fn merge(&mut self, source: T) -> Result<(), Box> - where T: SourceBuilder - { - self.sources.push(source.build()?); +// Underlying storage for the configuration +enum ConfigStore<'a> { + Mutable { + defaults: HashMap>, + overrides: HashMap>, + sources: Vec>, + }, + + // TODO: Will be used for frozen configuratino soon + #[allow(dead_code)] + Frozen, +} - Ok(()) +impl<'a> Default for ConfigStore<'a> { + fn default() -> Self { + ConfigStore::Mutable { + defaults: HashMap::new(), + overrides: HashMap::new(), + sources: Vec::new(), + } } +} - /// Sets the default value for this key. The default value is only used - /// when no other value is provided. - pub fn set_default(&mut self, key: &str, value: T) - where T: Into +impl<'a> ConfigStore<'a> { + fn merge(&mut self, source: T) -> Result<(), Box> + where T: SourceBuilder { - self.defaults.insert(key.to_lowercase(), value.into()); + if let ConfigStore::Mutable { ref mut sources, .. } = *self { + sources.push(source.build()?); + + Ok(()) + } else { + Err(FrozenError::default().into()) + } } - /// Sets an override for this key. - pub fn set(&mut self, key: &str, value: T) - where T: Into + fn set_default(&mut self, key: &str, value: T) -> Result<(), Box> + where T: Into> { - self.overrides.insert(key.to_lowercase(), value.into()); + if let ConfigStore::Mutable { ref mut defaults, .. } = *self { + defaults.insert(key.to_lowercase(), value.into()); + + Ok(()) + } else { + Err(FrozenError::default().into()) + } } - pub fn get(&self, key: &str) -> Option { - // Check explicit override + fn set(&mut self, key: &str, value: T) -> Result<(), Box> + where T: Into> + { + if let ConfigStore::Mutable { ref mut overrides, .. } = *self { + overrides.insert(key.to_lowercase(), value.into()); - if let Some(value) = self.overrides.get(key) { - return Some(value.clone()); + Ok(()) + } else { + Err(FrozenError::default().into()) } + } + + fn get(&self, key: &str) -> Option { + if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self { + // Check explicit override + if let Some(value) = overrides.get(key) { + return Some(value.clone()); + } - // Check sources + // Check sources + for source in &mut sources.iter().rev() { + if let Some(value) = source.get(key) { + return Some(value); + } + } - for source in &mut self.sources.iter().rev() { - if let Some(value) = source.get(key) { - return Some(value); + // Check explicit defaults + if let Some(value) = defaults.get(key) { + return Some(value.clone()); } } - // Check explicit defaults + None + } +} - if let Some(value) = self.defaults.get(key) { - return Some(value.clone()); - } +#[derive(Default)] +pub struct Config<'a> { + store: ConfigStore<'a>, +} +// TODO(@rust): There must be a way to remove this function and use Value::as_str +#[allow(needless_lifetimes)] +fn value_into_str<'a>(value: Value<'a>) -> Option> { + if let Value::String(value) = value { + Some(value) + } else if let Value::Integer(value) = value { + Some(Cow::Owned(value.to_string())) + } else if let Value::Float(value) = value { + Some(Cow::Owned(value.to_string())) + } else if let Value::Boolean(value) = value { + Some(Cow::Owned(value.to_string())) + } else { None } +} + +impl<'a> Config<'a> { + pub fn new() -> Config<'a> { + Default::default() + } + + /// Merge in configuration values from the given source. + pub fn merge(&mut self, source: T) -> Result<(), Box> + where T: SourceBuilder + { + self.store.merge(source) + } - pub fn get_str(&self, key: &str) -> Option { - self.get(key).and_then(Value::as_str) + /// Sets the default value for this key. The default value is only used + /// when no other value is provided. + pub fn set_default(&mut self, key: &str, value: T) -> Result<(), Box> + where T: Into> + { + self.store.set_default(key, value) + } + + /// Sets an override for this key. + pub fn set(&mut self, key: &str, value: T) -> Result<(), Box> + where T: Into> + { + self.store.set(key, value) + } + + pub fn get(&self, key: &str) -> Option> { + self.store.get(key).map(Cow::Owned) + } + + pub fn get_str(&'a self, key: &str) -> Option> { + // TODO(@rust): This is a bit nasty looking; 3x match and requires this + // odd into_str method + if let Some(value) = self.get(key) { + match value { + Cow::Borrowed(value) => { + match value.as_str() { + Some(value) => { + match value { + Cow::Borrowed(value) => Some(Cow::Borrowed(value)), + Cow::Owned(value) => Some(Cow::Owned(value)), + } + } + + _ => None, + } + } + + Cow::Owned(value) => value_into_str(value), + } + } else { + None + } } pub fn get_int(&self, key: &str) -> Option { - self.get(key).and_then(Value::as_int) + // TODO(@rust): Why doesn't .and_then(Value::as_int) work? + self.get(key).and_then(|v| v.as_int()) } pub fn get_float(&self, key: &str) -> Option { - self.get(key).and_then(Value::as_float) + // TODO(@rust): Why doesn't .and_then(Value::as_float) work? + self.get(key).and_then(|v| v.as_float()) } pub fn get_bool(&self, key: &str) -> Option { - self.get(key).and_then(Value::as_bool) + // TODO(@rust): Why doesn't .and_then(Value::as_bool) work? + self.get(key).and_then(|v| v.as_bool()) } } @@ -99,13 +216,13 @@ mod test { fn test_default_override() { let mut c = Config::new(); - c.set_default("key_1", false); - c.set_default("key_2", false); + c.set_default("key_1", false).unwrap(); + c.set_default("key_2", false).unwrap(); assert!(!c.get_bool("key_1").unwrap()); assert!(!c.get_bool("key_2").unwrap()); - c.set("key_2", true); + c.set("key_2", true).unwrap(); assert!(!c.get_bool("key_1").unwrap()); assert!(c.get_bool("key_2").unwrap()); @@ -116,7 +233,7 @@ mod test { fn test_str() { let mut c = Config::new(); - c.set("key", "value"); + c.set("key", "value").unwrap(); assert_eq!(c.get_str("key").unwrap(), "value"); } @@ -126,7 +243,7 @@ mod test { fn test_bool() { let mut c = Config::new(); - c.set("key", true); + c.set("key", true).unwrap(); assert_eq!(c.get_bool("key").unwrap(), true); } @@ -136,7 +253,7 @@ mod test { fn test_float() { let mut c = Config::new(); - c.set("key", 3.14); + c.set("key", 3.14).unwrap(); assert_eq!(c.get_float("key").unwrap(), 3.14); } @@ -146,7 +263,7 @@ mod test { fn test_int() { let mut c = Config::new(); - c.set("key", 42); + c.set("key", 42).unwrap(); assert_eq!(c.get_int("key").unwrap(), 42); } @@ -156,9 +273,9 @@ mod test { fn test_retrieve_str() { let mut c = Config::new(); - c.set("key_1", 115); - c.set("key_2", 1.23); - c.set("key_3", false); + c.set("key_1", 115).unwrap(); + c.set("key_2", 1.23).unwrap(); + c.set("key_3", false).unwrap(); assert_eq!(c.get_str("key_1").unwrap(), "115"); assert_eq!(c.get_str("key_2").unwrap(), "1.23"); @@ -170,12 +287,12 @@ mod test { fn test_retrieve_int() { let mut c = Config::new(); - c.set("key_1", "121"); - c.set("key_2", 5.12); - c.set("key_3", 5.72); - c.set("key_4", false); - c.set("key_5", true); - c.set("key_6", "asga"); + c.set("key_1", "121").unwrap(); + c.set("key_2", 5.12).unwrap(); + c.set("key_3", 5.72).unwrap(); + c.set("key_4", false).unwrap(); + c.set("key_5", true).unwrap(); + c.set("key_6", "asga").unwrap(); assert_eq!(c.get_int("key_1"), Some(121)); assert_eq!(c.get_int("key_2"), Some(5)); @@ -190,12 +307,12 @@ mod test { fn test_retrieve_float() { let mut c = Config::new(); - c.set("key_1", "121"); - c.set("key_2", "121.512"); - c.set("key_3", 5); - c.set("key_4", false); - c.set("key_5", true); - c.set("key_6", "asga"); + c.set("key_1", "121").unwrap(); + c.set("key_2", "121.512").unwrap(); + c.set("key_3", 5).unwrap(); + c.set("key_4", false).unwrap(); + c.set("key_5", true).unwrap(); + c.set("key_6", "asga").unwrap(); assert_eq!(c.get_float("key_1"), Some(121.0)); assert_eq!(c.get_float("key_2"), Some(121.512)); @@ -210,17 +327,17 @@ mod test { fn test_retrieve_bool() { let mut c = Config::new(); - c.set("key_1", "121"); - c.set("key_2", "1"); - c.set("key_3", "0"); - c.set("key_4", "true"); - c.set("key_5", ""); - c.set("key_6", 51); - c.set("key_7", 0); - c.set("key_8", 12.12); - c.set("key_9", 1.0); - c.set("key_10", 0.0); - c.set("key_11", "asga"); + c.set("key_1", "121").unwrap(); + c.set("key_2", "1").unwrap(); + c.set("key_3", "0").unwrap(); + c.set("key_4", "true").unwrap(); + c.set("key_5", "").unwrap(); + c.set("key_6", 51).unwrap(); + c.set("key_7", 0).unwrap(); + c.set("key_8", 12.12).unwrap(); + c.set("key_9", 1.0).unwrap(); + c.set("key_10", 0.0).unwrap(); + c.set("key_11", "asga").unwrap(); assert_eq!(c.get_bool("key_1"), None); assert_eq!(c.get_bool("key_2"), Some(true)); diff --git a/src/file/json.rs b/src/file/json.rs index 05af065..165bdbe 100644 --- a/src/file/json.rs +++ b/src/file/json.rs @@ -2,6 +2,7 @@ use serde_json; use source::Source; use std::error::Error; +use std::borrow::Cow; use value::Value; pub struct Content { @@ -20,7 +21,7 @@ impl Content { fn from_json_value(value: &serde_json::Value) -> Option { match *value { - serde_json::Value::String(ref value) => Some(Value::String(value.clone())), + serde_json::Value::String(ref value) => Some(Value::String(Cow::Borrowed(value))), serde_json::Value::Number(ref value) => { if let Some(value) = value.as_i64() { diff --git a/src/file/toml.rs b/src/file/toml.rs index 9e4b170..2fd5bd3 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -1,5 +1,6 @@ use toml; use source::Source; +use std::borrow::Cow; use std::error::Error; use value::Value; @@ -21,7 +22,7 @@ impl Content { fn from_toml_value(value: &toml::Value) -> Option { match *value { - toml::Value::String(ref value) => Some(Value::String(value.clone())), + toml::Value::String(ref value) => Some(Value::String(Cow::Borrowed(value))), toml::Value::Float(value) => Some(Value::Float(value)), toml::Value::Integer(value) => Some(Value::Integer(value)), toml::Value::Boolean(value) => Some(Value::Boolean(value)), diff --git a/src/lib.rs b/src/lib.rs index ad527e2..25e2255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![feature(drop_types_in_const)] + #![allow(unknown_lints)] #[cfg(feature = "toml")] @@ -14,7 +15,8 @@ mod env; mod config; use std::error::Error; -use std::sync::{Once, ONCE_INIT, RwLock}; +use std::sync::{Once, ONCE_INIT}; +use std::borrow::Cow; pub use source::{Source, SourceBuilder}; pub use file::{File, FileFormat}; @@ -25,13 +27,15 @@ pub use value::Value; pub use config::Config; // Global configuration -static mut CONFIG: Option> = None; +static mut CONFIG: Option> = None; static CONFIG_INIT: Once = ONCE_INIT; // Get the global configuration instance -fn global() -> &'static RwLock { +pub fn global() -> &'static mut Config<'static> { unsafe { - CONFIG_INIT.call_once(|| { CONFIG = Some(Default::default()); }); + CONFIG_INIT.call_once(|| { + CONFIG = Some(Default::default()); + }); CONFIG.as_mut().unwrap() } @@ -40,43 +44,37 @@ fn global() -> &'static RwLock { pub fn merge(source: T) -> Result<(), Box> where T: SourceBuilder { - global().write()?.merge(source) + global().merge(source) } pub fn set_default(key: &str, value: T) -> Result<(), Box> - where T: Into + where T: Into> { - global().write()?.set_default(key, value); - - // TODO: `set_default` will be able to fail soon so this will not be needed - Ok(()) + global().set_default(key, value) } pub fn set(key: &str, value: T) -> Result<(), Box> - where T: Into + where T: Into> { - global().write()?.set(key, value); - - // TODO: `set_default` will be able to fail soon so this will not be needed - Ok(()) + global().set(key, value) } -pub fn get(key: &str) -> Option { - global().read().unwrap().get(key) +pub fn get<'a>(key: &str) -> Option> { + global().get(key) } -pub fn get_str(key: &str) -> Option { - global().read().unwrap().get_str(key) +pub fn get_str<'a>(key: &str) -> Option> { + global().get_str(key) } pub fn get_int(key: &str) -> Option { - global().read().unwrap().get_int(key) + global().get_int(key) } pub fn get_float(key: &str) -> Option { - global().read().unwrap().get_float(key) + global().get_float(key) } pub fn get_bool(key: &str) -> Option { - global().read().unwrap().get_bool(key) + global().get_bool(key) } diff --git a/src/value.rs b/src/value.rs index a887104..ad173e8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,49 +1,53 @@ use std::convert::From; use std::collections::HashMap; +use std::borrow::Cow; /// A configuration value. /// /// Has an underlying or native type that comes from the configuration source /// but will be coerced into the requested type. #[derive(Debug, Clone)] -pub enum Value { - String(String), +pub enum Value<'a> { + String(Cow<'a, str>), Integer(i64), Float(f64), Boolean(bool), - Table(HashMap), - Array(Vec), + Table(HashMap>), + Array(Vec>), } -impl Value { - /// Gets the underyling value as a string, performing a conversion only if neccessary. - pub fn as_str(self) -> Option { - if let Value::String(value) = self { - Some(value) - } else if let Value::Integer(value) = self { - Some(value.to_string()) - } else if let Value::Float(value) = self { - Some(value.to_string()) - } else if let Value::Boolean(value) = self { - Some(value.to_string()) +impl<'a> Value<'a> { + /// Gets the underlying value as a string, performing a conversion only if neccessary. + pub fn as_str(&'a self) -> Option> { + if let Value::String(ref value) = *self { + Some(match *value { + Cow::Borrowed(v) => Cow::Borrowed(v), + Cow::Owned(ref v) => Cow::Borrowed(v), + }) + } else if let Value::Integer(value) = *self { + Some(Cow::Owned(value.to_string())) + } else if let Value::Float(value) = *self { + Some(Cow::Owned(value.to_string())) + } else if let Value::Boolean(value) = *self { + Some(Cow::Owned(value.to_string())) } else { None } } /// Gets the underlying type as a boolean, performing a conversion only if neccessary. - pub fn as_bool(self) -> Option { - if let Value::Boolean(value) = self { + pub fn as_bool(&self) -> Option { + if let Value::Boolean(value) = *self { Some(value) - } else if let Value::String(ref value) = self { + } else if let Value::String(ref value) = *self { match value.to_lowercase().as_ref() { "1" | "true" | "on" | "yes" => Some(true), "0" | "false" | "off" | "no" => Some(false), _ => None, } - } else if let Value::Integer(value) = self { + } else if let Value::Integer(value) = *self { Some(value != 0) - } else if let Value::Float(value) = self { + } else if let Value::Float(value) = *self { Some(value != 0.0) } else { None @@ -51,14 +55,14 @@ impl Value { } /// Gets the underlying type as an integer, performing a conversion only if neccessary. - pub fn as_int(self) -> Option { - if let Value::Integer(value) = self { + pub fn as_int(&self) -> Option { + if let Value::Integer(value) = *self { Some(value) - } else if let Value::String(ref value) = self { + } else if let Value::String(ref value) = *self { value.parse().ok() - } else if let Value::Boolean(value) = self { + } else if let Value::Boolean(value) = *self { Some(if value { 1 } else { 0 }) - } else if let Value::Float(value) = self { + } else if let Value::Float(value) = *self { Some(value.round() as i64) } else { None @@ -66,14 +70,14 @@ impl Value { } /// Gets the underlying type as a floating-point, performing a conversion only if neccessary. - pub fn as_float(self) -> Option { - if let Value::Float(value) = self { + pub fn as_float(&self) -> Option { + if let Value::Float(value) = *self { Some(value) - } else if let Value::String(ref value) = self { + } else if let Value::String(ref value) = *self { value.parse().ok() - } else if let Value::Integer(value) = self { + } else if let Value::Integer(value) = *self { Some(value as f64) - } else if let Value::Boolean(value) = self { + } else if let Value::Boolean(value) = *self { Some(if value { 1.0 } else { 0.0 }) } else { None @@ -84,32 +88,32 @@ impl Value { // Generalized construction from type into variant is needed // for setting configuration values -impl From for Value { - fn from(value: String) -> Value { - Value::String(value) +impl<'a> From for Value<'a> { + fn from(value: String) -> Value<'a> { + Value::String(value.into()) } } -impl<'a> From<&'a str> for Value { - fn from(value: &'a str) -> Value { +impl<'a> From<&'a str> for Value<'a> { + fn from(value: &'a str) -> Value<'a> { Value::String(value.into()) } } -impl From for Value { - fn from(value: i64) -> Value { +impl<'a> From for Value<'a> { + fn from(value: i64) -> Value<'a> { Value::Integer(value) } } -impl From for Value { - fn from(value: f64) -> Value { +impl<'a> From for Value<'a> { + fn from(value: f64) -> Value<'a> { Value::Float(value) } } -impl From for Value { - fn from(value: bool) -> Value { +impl<'a> From for Value<'a> { + fn from(value: bool) -> Value<'a> { Value::Boolean(value) } } -- cgit v1.2.3