diff --git a/benches/linear.rs b/benches/linear.rs index c16445e7..6854fd3c 100644 --- a/benches/linear.rs +++ b/benches/linear.rs @@ -23,11 +23,16 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("String lengths"); for length in [100, 200, 400, 800, 1600].iter() { let text = lorem_ipsum(*length); - let mut options = textwrap::Options::new(LINE_LENGTH); + let options = textwrap::Options::new(LINE_LENGTH); group.bench_with_input(BenchmarkId::new("fill", length), &text, |b, text| { b.iter(|| textwrap::fill(text, &options)); }); + let options: textwrap::Options = options.splitter(Box::new(textwrap::HyphenSplitter)); + group.bench_with_input(BenchmarkId::new("fill_boxed", length), &text, |b, text| { + b.iter(|| textwrap::fill(text, &options)); + }); + group.bench_with_input(BenchmarkId::new("fill_usize", length), &text, |b, text| { b.iter(|| textwrap::fill(text, LINE_LENGTH)); }); @@ -39,11 +44,11 @@ pub fn benchmark(c: &mut Criterion) { .join("benches") .join("la.standard.bincode"); let dictionary = Standard::from_path(Language::Latin, &path).unwrap(); - options.splitter = Box::new(dictionary); + let options = options.splitter(dictionary); group.bench_with_input(BenchmarkId::new("hyphenation", length), &text, |b, text| { b.iter(|| textwrap::fill(text, &options)); }); - } + }; } group.finish(); } diff --git a/examples/hyphenation.rs b/examples/hyphenation.rs index ce152fea..be6b63b7 100644 --- a/examples/hyphenation.rs +++ b/examples/hyphenation.rs @@ -12,6 +12,6 @@ fn main() { fn main() { let text = "textwrap: a small library for wrapping text."; let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = textwrap::Options::new(18).splitter(Box::new(dictionary)); + let options = textwrap::Options::new(18).splitter(dictionary); println!("{}", textwrap::fill(text, &options)); } diff --git a/examples/layout.rs b/examples/layout.rs index 04654d99..d775279c 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -1,4 +1,4 @@ -use textwrap::{wrap, Options}; +use textwrap::{wrap, HyphenSplitter, Options}; fn main() { let example = "Memory safety without garbage collection. \ @@ -6,7 +6,7 @@ fn main() { Zero-cost abstractions."; let mut prev_lines = vec![]; - let mut options = Options::new(0); + let mut options: Options = Options::new(0).splitter(Box::new(HyphenSplitter)); #[cfg(feature = "hyphenation")] { use hyphenation::Load; diff --git a/examples/termwidth.rs b/examples/termwidth.rs index 3044ad1b..c909cbc2 100644 --- a/examples/termwidth.rs +++ b/examples/termwidth.rs @@ -21,9 +21,9 @@ fn main() { #[cfg(feature = "hyphenation")] let (msg, options) = ( "with hyphenation", - Options::with_termwidth().splitter(Box::new( + Options::with_termwidth().splitter( hyphenation::Standard::from_embedded(hyphenation::Language::EnglishUS).unwrap(), - )), + ), ); println!("Formatted {} in {} columns:", msg, options.width); diff --git a/src/lib.rs b/src/lib.rs index b5fa1cda..597a8440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! fn main() { //! let text = "textwrap: a small library for wrapping text."; //! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); -//! let options = Options::new(18).splitter(Box::new(dictionary)); +//! let options = Options::new(18).splitter(dictionary); //! println!("{}", fill(text, &options)); //! } //! @@ -231,39 +231,76 @@ impl WrapOptions for usize { } /// Constructors for boxed Options, specifically. -impl<'a> Options<'a> { - /// Creates a new `Options` with the specified width and dynamic dispatch +impl<'a> Options<'a, HyphenSplitter> { + /// Creates a new `Options` with the specified width and static dispatch /// using the `HyphenSplitter`. Equivalent to /// /// ``` /// # use textwrap::{Options, HyphenSplitter, WordSplitter}; /// # let width = 80; /// # let actual = Options::new(width); - /// # let expected: Options = + /// # let expected = /// Options { /// width: width, /// initial_indent: "", /// subsequent_indent: "", /// break_words: true, - /// splitter: Box::new(HyphenSplitter), + /// splitter: HyphenSplitter, /// } /// # ; /// # assert_eq!(actual.width, expected.width); /// # assert_eq!(actual.initial_indent, expected.initial_indent); /// # assert_eq!(actual.subsequent_indent, expected.subsequent_indent); /// # assert_eq!(actual.break_words, expected.break_words); - /// # let expected_coerced: Options<'static, Box> = expected; + /// # let expected_coerced: Options<'static, HyphenSplitter> = expected; + /// ``` + /// + /// Static dispatch mean here, that the splitter is stored as-is and the + /// type is known at compile-time. Thus the returned value is actually a + /// `Options`. + /// + /// Dynamic dispatch on the other hand, mean that the splitter is stored as a trait object + /// for instance in a `Box`. This way the splitter's inner type + /// can be changed without changing the type of this struct, which then would be just + /// `Options` as a short cut for `Options>`. + /// + /// The value and type of the splitter can be choose from the start using the `with_splitter` + /// constructor or changed afterwards using the `splitter` method. Whether static or + /// dynamic dispatch is used, depends on whether these functions are given a boxed `WordSplitter` + /// or not. Take for example: + /// /// ``` + /// use textwrap::{Options, NoHyphenation, HyphenSplitter}; + /// # use textwrap::{WordSplitter}; + /// # let width = 80; /// - /// Dynamic dispatch means here, that the splitter is stored in a - /// `Box`, which allows to even change the splitter's - /// inner type without changing the type of this struct. If you instead - /// need more control of the type of the splitter, there is also the - /// `with_splitter` constructor, which allows you to specify a precise - /// splitter without the boxing. + /// // uses HyphenSplitter with static dispatch + /// // the actual type: Options + /// let opt = Options::new(width); + /// # let opt_coerce: Options = opt; /// - pub fn new(width: usize) -> Self { - Options::with_splitter(width, Box::new(HyphenSplitter)) + /// // uses NoHyphenation with static dispatch + /// // the actual type: Options + /// let opt = Options::new(width).splitter(NoHyphenation); + /// # let opt_coerce: Options = opt; + /// + /// // uses HyphenSplitter with dynamic dispatch + /// // the actual type: Options> + /// let opt: Options = Options::new(width).splitter(Box::new(HyphenSplitter)); + /// # let opt_coerce: Options> = opt; + /// + /// // uses NoHyphenation with dynamic dispatch + /// // the actual type: Options> + /// let opt: Options = Options::new(width).splitter(Box::new(NoHyphenation)); + /// # let opt_coerce: Options> = opt; + /// ``` + /// + /// Notice that the last two variables have the same type, despite the different `WordSplitter` + /// in use. Thus dynamic dispatch allows to change the splitter at run-time without changing + /// the variables type. + /// + pub const fn new(width: usize) -> Self { + Options::with_splitter(width, HyphenSplitter) } /// Creates a new `Options` with `width` set to the current @@ -294,7 +331,7 @@ impl<'a, S> Options<'a, S> { /// to /// /// ``` - /// # use textwrap::{Options, NoHyphenation}; + /// # use textwrap::{Options, NoHyphenation, HyphenSplitter}; /// # const splitter: NoHyphenation = NoHyphenation; /// # const width: usize = 80; /// # const actual: Options<'static, NoHyphenation> = Options::with_splitter(width, splitter); @@ -314,14 +351,13 @@ impl<'a, S> Options<'a, S> { /// # let expected_coerced: Options<'static, NoHyphenation> = expected; /// ``` /// - /// This constructor allows to specify the precise splitter to be used, and - /// let it stored as is in the struct, without the need for a box, unlike - /// `new`. However, this function can still be used with a box, for - /// instance, to obtain a boxed `Options` with a different splitter: + /// This constructor allows to specify the splitter to be used. It is like a short-cut for + /// `Options::new(w).splitter(s)`, but this function is a `const fn`. The given splitter may + /// be in a `Box`, which then can be coerced into a trait object for dynamic dispatch: /// /// ``` - /// use textwrap::{Options, NoHyphenation}; - /// # use textwrap::{WordSplitter, HyphenSplitter}; + /// use textwrap::{Options, NoHyphenation, HyphenSplitter}; + /// # use textwrap::{WordSplitter}; /// # const width: usize = 80; /// # let expected: Options = /// # Options { @@ -333,8 +369,9 @@ impl<'a, S> Options<'a, S> { /// # } /// # ; /// - /// // This opt has the same type as returned by `new`, but it contains a - /// // `NoHyphenation` splitter + /// // This opt contains a boxed trait object as splitter. + /// // Its type is actually: `Options>` + /// // The type annotation is important, otherwise it will be not a trait object /// let mut opt: Options = Options::with_splitter(width, Box::new(NoHyphenation)); /// # /// # let actual = opt; @@ -344,8 +381,8 @@ impl<'a, S> Options<'a, S> { /// # assert_eq!(actual.break_words, expected.break_words); /// # let expected_coerced: Options> = expected; /// - /// // Thus, it can be overridden. - /// opt = Options::new(width); + /// // Thus, it can be overridden with a different splitter. + /// opt = Options::with_splitter(width, Box::new(HyphenSplitter)); /// // Now, containing a `HyphenSplitter` instead. /// # /// # let expected: Options = @@ -398,12 +435,6 @@ impl<'a, S> Options<'a, S> { /// # let expected_coerced: &Options = actual; /// ``` /// - /// Notice, that initializing `const` and `static` can only be done using - /// `with_splitter` (or initializing the struct in place), because the - /// functional equivalent `Options::new(width).splitter(HyphenSplitter)` - /// allocates a `Box`, which can not be done for initializing `const` and - /// `static`. - /// pub const fn with_splitter(width: usize, splitter: S) -> Self { Options { width, @@ -479,12 +510,27 @@ impl<'a, S: WordSplitter> Options<'a, S> { /// Change [`self.splitter`]. The [`WordSplitter`] is used to fit /// part of a word into the current line when wrapping text. /// + /// This function may return a different type than `Self`. That is the case when the given + /// `splitter` is of a different type the the currently stored one in the `splitter` field. + /// Take for example: + /// + /// ``` + /// use textwrap::{Options, HyphenSplitter, NoHyphenation}; + /// // The default type returned by `new` is `Options` + /// let opt: Options = Options::new(80); + /// // Setting a different splitter changes the type + /// let opt: Options = opt.splitter(NoHyphenation); + /// ``` + /// /// [`self.splitter`]: #structfield.splitter /// [`WordSplitter`]: trait.WordSplitter.html - pub fn splitter(self, splitter: S) -> Self { + pub fn splitter(self, splitter: T) -> Options<'a, T> { Options { + width: self.width, + initial_indent: self.initial_indent, + subsequent_indent: self.subsequent_indent, + break_words: self.break_words, splitter: splitter, - ..self } } } @@ -708,7 +754,7 @@ impl<'input, T: WrapOptions> Iterator for WrapIter<'input, T> { /// /// let width = termwidth() - 4; // Two columns on each side. /// let options = Options::new(width) -/// .splitter(Box::new(NoHyphenation)) +/// .splitter(NoHyphenation) /// .initial_indent(" ") /// .subsequent_indent(" "); /// ``` @@ -1039,14 +1085,49 @@ mod tests { } #[test] - fn no_hyphenation() { - let options = Options::new(8).splitter(Box::new(NoHyphenation)); + fn simple_hyphens_static() { + let options = Options::new(8).splitter(HyphenSplitter); + assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]); + } + + #[test] + fn simple_hyphens_dynamic() { + let options: Options = Options::new(8).splitter(Box::new(HyphenSplitter)); + assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]); + } + + #[test] + fn no_hyphenation_static() { + let options = Options::new(8).splitter(NoHyphenation); + assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]); + } + + #[test] + fn no_hyphenation_dynamic() { + let options: Options = Options::new(8).splitter(Box::new(NoHyphenation)); assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]); } #[test] #[cfg(feature = "hyphenation")] - fn auto_hyphenation() { + fn auto_hyphenation_static() { + let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); + let options = Options::new(10); + assert_iter_eq!( + wrap("Internationalization", &options), + vec!["Internatio", "nalization"] + ); + + let options = Options::new(10).splitter(dictionary); + assert_iter_eq!( + wrap("Internationalization", &options), + vec!["Interna-", "tionaliza-", "tion"] + ); + } + + #[test] + #[cfg(feature = "hyphenation")] + fn auto_hyphenation_dynamic() { let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); let options = Options::new(10); assert_iter_eq!( @@ -1054,7 +1135,7 @@ mod tests { vec!["Internatio", "nalization"] ); - let options = Options::new(10).splitter(Box::new(dictionary)); + let options: Options = Options::new(10).splitter(Box::new(dictionary)); assert_iter_eq!( wrap("Internationalization", &options), vec!["Interna-", "tionaliza-", "tion"] @@ -1071,7 +1152,7 @@ mod tests { vec!["participat", "ion is the", "key to", "success"] ); - let options = Options::new(10).splitter(Box::new(dictionary)); + let options = Options::new(10).splitter(dictionary); assert_iter_eq!( wrap("participation is the key to success", &options), vec!["participa-", "tion is the", "key to", "success"] @@ -1084,7 +1165,7 @@ mod tests { // Test that hyphenation takes the width of the wihtespace // into account. let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(15).splitter(Box::new(dictionary)); + let options = Options::new(15).splitter(dictionary); assert_iter_eq!( wrap("garbage collection", &options), vec!["garbage col-", "lection"] @@ -1098,7 +1179,7 @@ mod tests { // line is borrowed. use std::borrow::Cow::{Borrowed, Owned}; let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(10).splitter(Box::new(dictionary)); + let options = Options::new(10).splitter(dictionary); let lines = wrap("Internationalization", &options).collect::>(); if let Borrowed(s) = lines[0] { assert!(false, "should not have been borrowed: {:?}", s); @@ -1121,7 +1202,7 @@ mod tests { vec!["over-", "caffinated"] ); - let options = options.splitter(Box::new(dictionary)); + let options = options.splitter(dictionary); assert_iter_eq!( wrap("over-caffinated", &options), vec!["over-", "caffi-", "nated"] diff --git a/src/splitting.rs b/src/splitting.rs index 6a39af25..617d24db 100644 --- a/src/splitting.rs +++ b/src/splitting.rs @@ -62,7 +62,7 @@ impl WordSplitter for Box { /// ``` /// use textwrap::{wrap, Options, NoHyphenation}; /// -/// let options = Options::new(8).splitter(Box::new(NoHyphenation)); +/// let options = Options::new(8).splitter(NoHyphenation); /// assert_eq!(wrap("foo bar-baz", &options).collect::>(), /// vec!["foo", "bar-baz"]); /// ```