Skip to content
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

Allow using trait objects with fill & wrap. #219

Merged
merged 1 commit into from
Nov 17, 2020

Conversation

Cryptjar
Copy link
Contributor

@Cryptjar Cryptjar commented Nov 5, 2020

Considering the issue #178 with the current state of master, using trait objects with fill & wrap seems to be a straight forward solution. However, until now, it is not supported by these functions. One problem is that they take the WrapOptions by-value, which is incompatible with trait objects.

Digging a bit deeper, it seems that the WrapIter already uses the WrapOptions by-value, but that trait only defines function that require access via reference. Additionally, considering that the WrapIter is typically a rather short-lived entity, it does seem justified to give it owned access to something it only borrows. This issue is also reflected by the implementation for Options, which as of now, implements WrapOptions only on a reference (i.e. &Options).

This PR rectifies this inconsistency by giving WrapIter only a reference to some WrapOptions. Now, it is no longer required that the given type is sized, and it directly allows for trait objects to be used.

However, the current implementation of this PR, has the drawback that the usize as WrapOptions must also be given by-reference so a simple fill("text", 42) becomes fill("text", &42), it shouldn't cause any issues, but the additional ampersand (&) isn't the prettiest.

As shown by a newly added test the following idiom using trait objects becomes now possible due to this PR:

// Storing the WrapOptions as a boxed trait object. (notice, also `Rc`, `Arc`, and others are possible)
let mut wrapper: Box<dyn WrapOptions> = Box::new(Options::new(80));

assert_iter_eq!(wrap("foo bar baz", &*wrapper), vec!["foo bar baz"]);

// Replace the `Options` with a `usize`
wrapper = Box::new(5);

assert_iter_eq!(wrap("foo bar baz", &*wrapper), vec!["foo", "bar", "baz"]);

This is a follow-up to my early comment, it implements what I called 'proper outer boxing' #215 (comment)

@mgeisler
Copy link
Owner

mgeisler commented Nov 5, 2020

Hey @Cryptjar,

As you note, fill("my text", &42) isn't the prettiest API — in fact, I don't like it very much :-) I should post an update on #178: I think the issue there has been solved in master already. Let's discuss on the issue!

About changes to WrapIter: I've been working on code which rewrites the wrapping code completely. This is to address #126, and I'll put up a PR for that soonish. The WrapIter goes away completely and is replaced by a few simpler functions.

@Cryptjar
Copy link
Contributor Author

Cryptjar commented Nov 9, 2020

Now, since you mentioned in #215 that you pushed your rewrite & removal of the WrapIter, I revisited this issue and still found it unresolved. I could have just update this PR to the current master version, but since you said that you don't like the referencing of usize, I actually rewrote this entire PR (which wasn't that much to begin with) to a different approach:

Now, instead of altering the fill & wrap function signatures, I kept them as-is, and instead add a new generic impl of WrapOptions for all references &T where T already implements WrapOptions. This allows now to pass all kind of references to fill & wrap including reference to trait objects.

Due to the generic impl, I also changed the primary impl of WrapOptions for Options from its reference to its value (i.e. from impl WrapOptions for &Options to impl WrapOptions for Options). The generic impl on all references then still allows to pass a reference of Options to fill & wrap (i.e. most (if not all) use-cases should continue to work). But since now Options by-value also implements WrapOptions some cases where a double reference to an Options were required, could be replaced now to a simple reference.

The example from my initial PR comment still works unchanged.

As a side note: this new approach might sound a bit similar to #217, but instead this PR does not introduce a general (blanked) impl for every T (every type ever), but only on the specific type 'reference' (&T), which in turn just happens to be generic. So it doesn't conflict with other impls.

@mgeisler
Copy link
Owner

Hey @Cryptjar,

Now, instead of altering the fill & wrap function signatures, I kept them as-is, and instead add a new generic impl of WrapOptions for all references &T where T already implements WrapOptions. This allows now to pass all kind of references to fill & wrap including reference to trait objects.

Thanks for the rewrite, this sounds great!

Due to the generic impl, I also changed the primary impl of WrapOptions for Options from its reference to its value (i.e. from impl WrapOptions for &Options to impl WrapOptions for Options). The generic impl on all references then still allows to pass a reference of Options to fill & wrap (i.e. most (if not all) use-cases should continue to work). But since now Options by-value also implements WrapOptions some cases where a double reference to an Options were required, could be replaced now to a simple reference.

This also sounds very nice 👍

However, the changes you propose here overlap with changes proposed by @Koxiaet in #227. I believe you're both basically doing the same thing with blanket implementations for WrapOptions for &T where T: WrapOptions. Are such blanket implementations a normal thing to have?

It seems strange to me — as I wrote on #227, this is similar to implementing the * operator :-) Does Rust not already have the auto-deref functionality for this? Or am I mixing things up somehow?


Perhaps we can go a step back:

Now, since you mentioned in #215 that you pushed your rewrite & removal of the WrapIter, I revisited this issue and still found it unresolved.

I'm not 100% sure which "issue" you're referring to here... Is it #178? It's still open since I'm hoping to hear back from the author — as far as I know, it's now possible to embed an Options in a struct and so the issue can be closed.

@Cryptjar
Copy link
Contributor Author

I'm not 100% sure which "issue" you're referring to here... Is it #178? It's still open since I'm hoping to hear back from the author — as far as I know, it's now possible to embed an Options in a struct and so the issue can be closed.

Well, what I meant is related to #178 since it showed the symptoms of that issue that I'm referring to. By which I meant, since there is now a trait WrapOptions that can be arbitrarily implemented by any user, the most general thingy to describe wrapping parameters is anything that implements WrapOptions (so is the argument of fill & wrap any T: WrapOptions which is more than just usize & Options).

However, if one is writing a library crate (such as it appears to me, to be the case in #178), one might want to store arbitrary user-provided wrapping parameters (aka some WrapOptions) in a struct, which might has to be without generics. At this point, in order to allow to store all possible implementations, one way is to use trait-objects (i.e. some dyn WrapOptions), which is more general than a simple Options<Box<dyn WordSplitter>> (e.g. it would also allow usize).

The problem (or issue) is precisely that as of now, once you got a trait-object (e.g. Box<dyn WrapOptions>) there is no way to use it with fill & wrap. And that is what this PR tries to solve.

However, the changes you propose here overlap with changes proposed by @Koxiaet in #227.

Interestingly, since Options lets you freely define all the return values of WrapOptions, he got a point, that the trait seems a bit superfluous from a start.

I believe you're both basically doing the same thing with blanket implementations for WrapOptions for &T where T: WrapOptions. Are such blanket implementations a normal thing to have?

It seems strange to me — as I wrote on #227, this is similar to implementing the * operator :-) Does Rust not already have the auto-deref functionality for this? Or am I mixing things up somehow?

(those are strictly speaking not blanket implementations but generic impls on the generic reference type)

Rust does have the auto-deref functionality (especially in the context of the *-operator), but it only applies to references. For example, if you want to pass a Box<Options> into fill, you got the problem, that Box does not implement WrapOptions, no Deref involved. But what you really want to do is actually to only pass a reference (&Box<Options>) which now thanks to Deref can auto-deref to a simple &Options and that happens to implement WrapOptions.

That is nice and cozy, but it does not help with a trait-object: if you have Box<Options> you can coerce it into a Box<dyn WrapOptions> and when you have a reference to it, it can auto-deref to a simple &dyn WrapOptions but no further. And sadly, as of now there is no implementation of WrapOptions for &dyn WrapOptions (whereas dyn WrapOptions implements WrapOptions, notice the missing reference). And that is what I added in this PR: an implementation of WrapOptions for references.

On the other hand, it does not seem that @Koxiaet cares much about trait objects in his PR, but instead his generic implementation seems to have to do with his From implementation.

@mgeisler
Copy link
Owner

The problem (or issue) is precisely that as of now, once you got a trait-object (e.g. Box<dyn WrapOptions>) there is no way to use it with fill & wrap. And that is what this PR tries to solve.

Thanks, I see now!

However, the changes you propose here overlap with changes proposed by @Koxiaet in #227.

Interestingly, since Options lets you freely define all the return values of WrapOptions, he got a point, that the trait seems a bit superfluous from a start.

Yeah, I agree and I will like to merge the changes there so we can remove WrapOptions and work with the From trait instead. However, I'm not sure how this plays into the changes in this PR.

Basically, #277 adds a

impl From<usize> for Options<'static, HyphenSplitter> {
    fn from(width: usize) -> Self {
        Options::new(width)
    }
}

and changes fill and wrap to take an into<Options> argument by value:

pub fn fill<'a, S: WordSplitter, T: Into<Options<'a, S>>>(text: &str, options: T) -> String {
    // ...
}

On the other hand, this PR adds a WrapOptions implementation for all &T where T: WrapOptions:

impl<T: ?Sized + WrapOptions> WrapOptions for &T {
    // ...
}

Can this be combined with the above? This compiles, but I haven't checked if it does the right thing...

impl<'a, T: ?Sized + Into<Options<'a>>> From<&T> for Options<'a> {
    fn from(other: &T) -> Self {
        other.into()
    }
}

@mgeisler
Copy link
Owner

That is nice and cozy, but it does not help with a trait-object: if you have Box<Options> you can coerce it into a Box<dyn WrapOptions> and when you have a reference to it, it can auto-deref to a simple &dyn WrapOptions but no further. And sadly, as of now there is no implementation of WrapOptions for &dyn WrapOptions (whereas dyn WrapOptions implements WrapOptions, notice the missing reference). And that is what I added in this PR: an implementation of WrapOptions for references.

This is a really good explanation, thanks! I'm learning a lot here 😄

@mgeisler
Copy link
Owner

I've merged #227, so the WrapOptions trait is no more and things should be a little bit cleaner now. I hope we can then sort out this PR next :-)

In order to support turning a `Options<T>` into a trait object like `Options<dyn WordSplitter>`,
the requirement of `Options` for a sized `WordSplitter` has been lifted.

Some unit tests and an example have been added.
@Cryptjar
Copy link
Contributor Author

Cryptjar commented Nov 15, 2020

Sure. The point now is that without the WrapOptions trait (which has been removed by #227) there is actually no point in make it into a trait-object. And without the trait, there can be no user implementations of it either, and the most general configuration parameters can be truly represented by a simple Options.

However, for the library use-case I mentioned earlier, it still seems interesting to have some trait-object support such as an Options<dyn WordSplitter>. So I rewrote this PR again to now allow this.

This now third approach is what I used to call the 'outer boxing' (i.e. Box<Option<dyn WordSplitter>>) in #215, which is similar but different from what I called 'inner boxing' (i.e. Option<Box<dyn WordSplitter>>) that already works. To highlight their distinct use-cases, I added now also an example multi-layouts, which demonstrates how a library might handle user provided Options (i.e. storing them in a Vec). This different from what the interactive example does (it only has one Option where the splitter is mutated in-place). So both examples now demonstrate their respective use-case and utilize 'outer boxing' and 'inner boxing' respectively.

@mgeisler mgeisler merged commit 9abb53c into mgeisler:master Nov 17, 2020
@mgeisler
Copy link
Owner

Excellent, thanks for the extensive explanations! They'll be super helpful when I link to the PR in the release notes.

mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
mgeisler added a commit that referenced this pull request May 1, 2021
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants