-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reintroducing the type parameter on Options
(previously known as Wrapper
).
#215
Conversation
Hi @Cryptjar, Thanks for catching this! I had not thought about how changing to As for your questions,
The situation that I to avoid is code like this enum Wrapper {
H(textwrap::Wrapper<'static, Standard>),
N(textwrap::Wrapper<'static, textwrap::NoHyphenation>),
} from gst-plugins-rs, discussed in #178. I think I would lean towards let
Are you suggesting adding an implementation for
Yeah, good question... I've so far though of the However, I'm very much curious to hear of better ways to design this.
That would be nice 👍 |
self.deref().split(word) | ||
} | ||
} | ||
/* Alternative, also adds impls for specific Box<S> i.e. Box<HyphenSplitter> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this was the example you mentioned in your PR description. I'm not sure exactly what the pros/cons are of these two options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll be happy to simply leave this out for now and then merge the rest — then we can make forward progress.
Thanks for writing a great commit message in your first commit — if you could squash the formatting commit into the first, then I'll be happy to merge this. No big hurry, though, since I also want to redo the internal wrapping algorithm before I make a new release. I hope to have this out in a week or so. |
Sorry, for late response.
Well, I don't know which one is good design, but there seems to be three reasonable ways to design the
impl<'a, S> Options<'a, S> {
/// No type changes allowed, works for all `S`, requires a `Box<dyn _>`
/// when coming from `new`
pub fn splitter(self, splitter: S) -> Self {
Options {
splitter: splitter,
..self
}
}
}
impl<'a, S> Options<'a, S> {
/// Allows type changes, works for all `S` & `T`, requires a `Box<dyn _>`
/// when it should become `Options<Box<dyn _>>`
pub fn splitter<T>(self, splitter: T) -> Options<T> {
Options {
width: self.width,
initial_indent: self.initial_indent,
subsequent_indent: self.subsequent_indent,
break_words: self.break_words,
splitter: splitter,
}
}
}
impl<'a> Options<'a> {
/// No type changes allowed, works only on `Options<Box<dyn _>>` (such as
/// get from `new`), but allows all `T`, which is automatically boxed
pub fn splitter<T: WordSplitter + 'static>(self, splitter: T) -> Self {
Options {
splitter: Box::new(splitter),
..self
}
}
} While the 1st version is simpler to implement it is the less useful one, since it does not allow to change the type (so has the same effect as reassigning the field). The 2nd seems more useful (as it does more than just reassigning the field, i.e. the type change), but might be easier done by directly using the Otherwise, it is pretty much a design question, implementable either way, or even, since there is now a |
Ok, I tried the 2nd version, and got a problem with the tests. Take for instance this one: fn no_hyphenation() {
let options = Options::new(8).splitter(Box::new(NoHyphenation));
assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
} It would compile just fine, as did up until now, with the 1st version, since Now, with version 2, the As a note, with version 3, the tests also need to be rewritten to remove the I think, this kinda showcases the look&feel of the API for the different version, so I went on and made it into a list: // Version 1, dynamic dispatch
let options = Options::new(8).splitter(Box::new(NoHyphenation));
// Version 1, static dispatch (falling back to `with_splitter`)
let options = Options::with_splitter(8, NoHyphenation);
// Version 2, dynamic dispatch
let options: Options = Options::new(8).splitter(Box::new(NoHyphenation));
// Version 2, static dispatch
let options = Options::new(8).splitter(NoHyphenation);
// Version 3, dynamic dispatch
let options = Options::new(8).splitter(NoHyphenation);
// Version 3, static dispatch (falling back to `with_splitter`)
let options = Options::with_splitter(8, NoHyphenation); Apparently only version 2 allows the dynamic dispatch type of |
Considering the problem of #178 at hand, there are essentially two ways to tackle it, I'm not sure whether they were both considered. First, what I gonna call 'inner boxing' is what #206 introduced. But when I noticed, that the However, I figured, that this 'outter boxing' strategy is fairly simple to implement, so I went to implement it for the I essentially just wanted to show off that option. Honestly, because, if that 'outer boxing' would be adopted instead of the here currently implemented 'inner boxing', one could remove the spacial cases of the So in summary, I don't think there is anything wrong with using the 'internal boxing' as it was introduced in #206 and still persists in this PR, but in case that alternative had not been considered before, now (before it gets released) is probably the best time to so. Regarding the API, I thought I make another list: // 'inner boxing' (version 2)
// dynamic dispatch
let options: Options<Box<dyn WordSplitter>> = Options::new(8).splitter(Box::new(NoHyphenation));
// static dispatch
let options: Options<NoHyphenation> = Options::new(8).splitter(NoHyphenation);
// 'outer boxing'
// dynamic dispatch
let options: Box<Options<dyn WordSplitter>> = Box::new(Options::new(8).splitter(NoHyphenation));
// static dispatch
let options: Options<NoHyphenation> = Options::new(8).splitter(NoHyphenation); For the 'inner boxing', I chose the 2nd version since it has the most consistent API, which becomes even more apparent when comparing it to the 'outer boxing' approach. I guess it makes it quite obvious why I call it 'inner' & 'outer' boxing. One thing to notice tho, in my opinion, using the 'outer boxing' approach make the usage of the actual |
Hi @Cryptjar, thanks for the updates! I'll try to take a look at it tomorrow — I think I roughly understand what's going on, otherwise I'll ask you for help :-) The "outer boxing" API does indeed look very nice and consistent 👍 |
I consider that to be a convenience method that people can use if they don't have any particular fancy requirements. If they do, then I'm fine with asking them to use a more complicated and advanced API.
If I understand correctly, the |
Nope, they weren't — I have yet to work with an unsized type of my own. It seems like an advanced type, so my gut instinct would be to avoid it unless it's really necessary because it brings some great benefit.
Thanks for adding the tests, very useful! I just pushed a little demo program that I've been working on for some time: #218. In the example, I have code like this:
That is, there is one I think the approach you suggest in this PR is compatible with such code — I believe this PR simply adds the possibility of static dispatch in addition to dynamic dispatch via a boxed Does the approach in the |
Fair enough. Actually, the
Functional it is true, but unfortunately there is no way to make the However, the problem with the impl<'a, S: ?Sized> Options<'a, S> {
pub const fn splitter<T>(&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,
}
}
}
// Assuming `new` is a `const fn` too, i.e. static dispatch
static HYP: Options<HyphenSplitter> = Options::new(10);
// Appearently no `drop` is called, and `splitter` just generate a new Options
static NO_HYP: Options<NoHyphenation> = HYP.splitter(NoHyphenation);
// However, this works too, due to some Rust magic.
static NO_HYP_2: Options<NoHyphenation> = Options::new(20).splitter(NoHyphenation);
Well, strictly speaking the approach in the
Very interesting. Good demo & good use-case.
Yes, that is true, this PR right now is even backwards compatible (with the previous commit).
Those (except for 1), would strictly speaking be no longer backwards compatible, tho it wouldn't impact functionality.
That gave me some hard time thinking. Eventually, I concluded that it is not possible with the It got me further thinking: with that approach, it actually is a bit hard to just replace the splitter (assuming dynamic dispatch) in general, since once you got a // assuming the `impl-outside-boxing-in-0.12` with the splitter method from above
#[test]
fn boxing_outside_replace_splitter() {
let mut dyn_box: Box<Wrapper<dyn WordSplitter>> = Box::new(Wrapper::new(10));
assert_eq!(dyn_box.wrap("foo bar-baz"), vec!["foo bar-", "baz"]);
// Just replace the splitter keeping the rest of the configuration in place
dyn_box = Box::new(dyn_box.splitter(NoHyphenation));
assert_eq!(dyn_box.wrap("foo bar-baz"), vec!["foo", "bar-baz"]);
} So, that Finally, I just wanted note further, tho I state the 'outer boxing' and 'inner boxing' as different approaches (which they are when solving a concrete problem), they are not mutually exclusive for a library: i.e. one could image to allow a unsized |
Well, I thought about these issues again. And it seems to me, that there are actually two different use-cases if comes to trait objects (dynamic dispatch), besides the more traditional use-cases where static dispatch is sufficient:
One point to notice here, is that in 1st use-case 'library style' it is preferable to allow the broadest set of implementation & configurations, thus a On the other hand, in 2nd use-case 'dynamic configuration' one does want to actively alter the configuration, thus using a general trait object (like a Also notice, that a In summary, I would recommend, to support both use-cases the 'inner boxing' and the 'proper outer boxing'. I will open a new PR for implementing the 'proper outer boxing'. However, when considering both, I would change the API to static dispatch by default, i.e. changing Two reasons here. First, static dispatch didn't seem to be an issue for the average user, and it is the only supported 'operation mode' in v0.12, so everyone using textwrap today is using static dispatch. It does not seem very reasonable to me to change that. Second, when the default would be dynamic dispatch via inner boxing, most people would going to use it (simply because |
I update this PR now to use static dispatch by default (962455c). In this context I made the
|
Thanks so much for the updates! I think it looks really good now. I like how you're able to basically remove all the noisy I've just merged my rewrite which dropped |
This commit reverts the internal changes of mgeisler#206, but keeps the public API compatible to what it introduced. Essetially by adding a default for the type parameter. However, now in addition to the dynamic dispatch by default, one may also explicitly use static dispatch by specifying the type parameter. This now allows to construct an `Options` instance in const/static context. Which is further facilitated by adding the const fn `with_splitter`. Also, since `Options` now may be some specific type the `Clone` derive is added again. So when `S` is `Clone` then `Options` is `Clone` too. This change is actually backwards compatible with the previous commit, as seen by the unchanged examples and tests.
This commit essentially changes the return type of `Options::new` from `Options<Box<dyn WordSplitter>>` to `Options<HyphenSplitter>`, also making it a `const fn` in the process. Also the `Options::splitter` method is changed in order to allow returning a `Options` with a different type parameter, which is sort of necessary for static dispatch. However, this also allows to change to dynamic dispatch. This also make the `Options::new(w).splitter(s)` combination consistently equivalent to `Options::with_splitter(w, s)`, tho only the latter is a `const fn`. Also test cases have been adapted to favor static dispatch.
I just rebased this PR. |
Thank you for the patience with the repeated changes and thanks for fixing a problem I didn't even realize I had introduced :-) |
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not cause big problems for Textwrap clients.
In #219, the type parameter for `Options` was relaxed to `?Sized`, which means that `Options<T>` can be a dynamically-sized type by using `dyn WordSplitter` as the type parameter. This allows code to freely assign both boxed and unboxed `WordSplitter`s to the same variable: ```rust let mut dynamic_opt: Box<Options<dyn WordSplitter>>; dynamic_opt = Box::new(Options::with_splitter(20, NoHyphenation)); dynamic_opt = Box::new(Options::with_splitter(20, Box::new(NoHyphenation))); ``` In both cases, dynamic dispatch would be used at runtime. This was called “proper outer boxing” in #219 and #215. By only boxing the word splitter (so-called “inner boxing”), the outer layer of indirection can be removed: ```rust let mut dynamic_opt: Options<Box<dyn WordSplitter>>; dynamic_opt = Options::with_splitter(20, Box::new(NoHyphenation)); dynamic_opt = Options::with_splitter(20, Box::new(HyphenSplitter)); ``` This also used dynamic dispatch at runtime. Static dispatching was also possible by using a fixed type. Trying to change the word splitter type is a compile time error: ```rust let mut static_opt: Options<NoHyphenation>; static_opt = Options::with_splitter(10, NoHyphenation); static_opt = Options::with_splitter(20, HyphenSplitter); // <- error! ``` In order to add a trait for the `WrapAlgorithm` enum (see #325), we’re now removing the `?Sized` bound on the `WordSplitter` type parameter. This makes the first block above a compile time error and you must now choose upfront between boxed and unboxed word splitters. If you choose a boxed word splitter (second example), then you get dynamic dispatch and can freely change the word splitter at runtime. If you choose an unboxed wordsplitter, you get static dispatch and cannot change the word splitter later. This change seems necessary since we will be adding more type parameters to the `Options` struct: one for the wrapping algorithm used and one for how we find words (splitting by space or by the full Unicode line breaking algorithm). Since both dynamic and static dispatch remains possible, this change should not fundamentally change the expressive power of the library.
This PR reverts the internal changes of #206, but keeps the public API compatible to what it introduced. Essentially by adding a default for the type parameter.
However, now in addition to the dynamic dispatch by default, one may also explicitly use static dispatch by specifying a concrete type parameter. This now allows to construct an
Options
instance in const/static context (or introduces, it depending on the point of view, as it was possible before #206). Which is further facilitated by adding the const fnsnew_const
andwith_splitter
.Open Questions
Options::new_const
could be changed (maybe tonew_static
). Or, for instance, theOptions::new_const
could replace the currentOptions::new
entirely, reverting to a static dispatch by default. Then instead, aOptions::new_boxed
(ornew_dyn
or something) could be added which then creates a boxedOptions
with dynamic dispatch. Yet another alternative, would be to just removeOptions::new_const
, since the fundamental use-case (const fn) is also covered byOptions::with_splitter
.WordSplitter
only forBox<dyn WordSplitter>
or for allBox<S>
splitter
setter method only on the dynamic dispatch variant (akaOptions<Box<dyn WordSplitter>>
). Then, it might also take an arbitraryimpl WordSplitter
by value and put it into aBox
.Options
Clone
again. Well, at least where the type parameter isClone
, which it isn't for the defaultBox<dyn WordSplitter>
.comments & other suggestions are welcome