book icon indicating copy to clipboard operation
book copied to clipboard

In 17.2 (Using Trait Objects...) the text for the example comparing trait objects with generics is incorrect.

Open jumpnbrownweasel opened this issue 1 year ago • 1 comments

  • I have searched open and closed issues and pull requests for duplicates, using these search terms:

    • trait object 17.2
  • I have checked the latest main branch to see if this has already been fixed, in this file:

    • https://github.com/rust-lang/book/blob/main/src/ch17-02-trait-objects.md

URL to the section(s) of the book with this problem:

https://doc.rust-lang.org/book/ch17-02-trait-objects.html#defining-a-trait-for-common-behavior https://doc.rust-lang.org/book/ch17-02-trait-objects.html#implementing-the-trait

Description of the problem:

The last paragraph of Defining a Trait for Common Behavior:

On the other hand, with the method using trait objects, one Screen instance can hold a Vec<T> that contains a Box<Button> as well as a Box<TextField>. Let’s look at how this works, and then we’ll talk about the runtime performance implications.

is incorrect, or at the least extremely misleading: Vec<T> should be Vec<Box<dyn Draw>>. Vec<T> is the type in the example code that uses generics, not trait objects, while Vec<Box<dyn Draw>> must be used in the example code using trait objects. The paragraph above is referring to the approach for trait objects, not generics.

This mistake leads the reader to think that Vec<T> should be used with the example code for trait objects. This adds to the confusion caused by another issue with the text in this section, which I believe should not have been closed.

Suggested fix: A minimal fix is to change Vec<T> to Vec<Box<dyn Draw>> in the paragraph mentioned above.

In addition I think it would avoid a lot of confusion (see this URLO post) to repeat the following type near the top of the Implementing the Trait section:

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

This would make it clear that the Implementing the Trait section is referring to this type, not the last shown definition of that type (which uses Vec<T>). The example code in this section will not compile if Vec<T> is used.

I would be happy to take a shot at fixing this if it is approved.

jumpnbrownweasel avatar Nov 15 '23 18:11 jumpnbrownweasel

Hmm, I see the confusion here, but I think there is actually a deeper level of confusion highlighted by your comment. (To be extra clear, I am not saying this is your fault or problem!) When you write this—

The paragraph above is referring to the approach for trait objects, not generics.

—it indicates that you saw trait objects as being an alternative to generics. The text more or less says that, so: fair! But they are not—at least, not exactly. They are a different feature, which can be used with generics, and I think the confusion here (as well as in the other issue you opened) is due to the two related, but ultimately distinct, ways that the text uses “generics” here: to refer to Vec<T> as a component of Screen, and how to define Screen itself—with a generic using a trait bound, or just requiring a trait object. That is, the difference the text cares about is:

  • Screen using trait object:

    pub struct Screen {
        pub components: Vec<Box<dyn Draw>>,
    }
    
  • Screen using constrained generic:

    pub struct Screen<T: Draw> {
        pub components: Vec<T>,
    }
    

The thing the text is distinguishing in this section is thus not between generics and trait objects per se, but rather two different ways you can specify what the generic type THIS TYPE is in the definition components: Vec<THIS TYPE>. The distinction is between using a trait objects there vs. a generic from Screen<T> with a trait bound. So I can see why at least some people are getting stuck here!

Because Vec<T> and Box<T> remain the normal ways to refer to those generic types, we might want to find a way to be a tiny bit clearer about when we are talking about them vs. when we are talking about Screen vs. Screen<T>. :thinking:

[!note] When you write Vec<Box<dyn Draw>>, you are still writing a specific instantiation of a generic type Vec<T>, as well as of the generic type Box<T>. Vec is always a “generic type”, generic over whatever T a given usage instantiates it with. (So is Box.) One way you can get a concrete type for it is with a “static” type like String, for example with Vec<String>. Another is with a pointer wrapping dyn Trait, for example with Vec<Box<dyn Draw>>. Both of those are a specific kind of Vec<T>. The text is trying to walk a fine line between precision/pedantry and saying just enough to cover the basic idea and keep moving toward the goal.

chriskrycho avatar Apr 01 '24 18:04 chriskrycho