diff --git a/compiler/rustc_error_codes/src/error_codes/E0038.md b/compiler/rustc_error_codes/src/error_codes/E0038.md
index 019d54b6202..ca2eaa54057 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0038.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0038.md
@@ -1,34 +1,64 @@
-Trait objects like `Box<Trait>` can only be constructed when certain
-requirements are satisfied by the trait in question.
+For any given trait `Trait` there may be a related _type_ called the _trait
+object type_ which is typically written as `dyn Trait`. In earlier editions of
+Rust, trait object types were written as plain `Trait` (just the name of the
+trait, written in type positions) but this was a bit too confusing, so we now
+write `dyn Trait`.
 
-Trait objects are a form of dynamic dispatch and use a dynamically sized type
-for the inner type. So, for a given trait `Trait`, when `Trait` is treated as a
-type, as in `Box<Trait>`, the inner type is 'unsized'. In such cases the boxed
-pointer is a 'fat pointer' that contains an extra pointer to a table of methods
-(among other things) for dynamic dispatch. This design mandates some
-restrictions on the types of traits that are allowed to be used in trait
-objects, which are collectively termed as 'object safety' rules.
+Some traits are not allowed to be used as trait object types. The traits that
+are allowed to be used as trait object types are called "object-safe" traits.
+Attempting to use a trait object type for a trait that is not object-safe will
+trigger error E0038.
 
-Attempting to create a trait object for a non object-safe trait will trigger
-this error.
+Two general aspects of trait object types give rise to the restrictions:
 
-There are various rules:
+  1. Trait object types are dynamically sized types (DSTs), and trait objects of
+     these types can only be accessed through pointers, such as `&dyn Trait` or
+     `Box<dyn Trait>`. The size of such a pointer is known, but the size of the
+     `dyn Trait` object pointed-to by the pointer is _opaque_ to code working
+     with it, and different tait objects with the same trait object type may
+     have different sizes.
 
-### The trait cannot require `Self: Sized`
+  2. The pointer used to access a trait object is paired with an extra pointer
+     to a "virtual method table" or "vtable", which is used to implement dynamic
+     dispatch to the object's implementations of the trait's methods. There is a
+     single such vtable for each trait implementation, but different trait
+     objects with the same trait object type may point to vtables from different
+     implementations.
 
-When `Trait` is treated as a type, the type does not implement the special
-`Sized` trait, because the type does not have a known size at compile time and
-can only be accessed behind a pointer. Thus, if we have a trait like the
-following:
+The specific conditions that violate object-safety follow, most of which relate
+to missing size information and vtable polymorphism arising from these aspects.
+
+### The trait requires `Self: Sized`
+
+Traits that are declared as `Trait: Sized` or which otherwise inherit a
+constraint of `Self:Sized` are not object-safe.
+
+The reasoning behind this is somewhat subtle. It derives from the fact that Rust
+requires (and defines) that every trait object type `dyn Trait` automatically
+implements `Trait`. Rust does this to simplify error reporting and ease
+interoperation between static and dynamic polymorphism. For example, this code
+works:
 
 ```
-trait Foo where Self: Sized {
+trait Trait {
+}
 
+fn static_foo<T:Trait + ?Sized>(b: &T) {
+}
+
+fn dynamic_bar(a: &dyn Trait) {
+    static_foo(a)
 }
 ```
 
-We cannot create an object of type `Box<Foo>` or `&Foo` since in this case
-`Self` would not be `Sized`.
+This code works because `dyn Trait`, if it exists, always implements `Trait`.
+
+However as we know, any `dyn Trait` is also unsized, and so it can never
+implement a sized trait like `Trait:Sized`. So, rather than allow an exception
+to the rule that `dyn Trait` always implements `Trait`, Rust chooses to prohibit
+such a `dyn Trait` from existing at all.
+
+Only unsized traits are considered object-safe.
 
 Generally, `Self: Sized` is used to indicate that the trait should not be used
 as a trait object. If the trait comes from your own crate, consider removing
@@ -67,7 +97,7 @@ trait Trait {
     fn foo(&self) -> Self;
 }
 
-fn call_foo(x: Box<Trait>) {
+fn call_foo(x: Box<dyn Trait>) {
     let y = x.foo(); // What type is y?
     // ...
 }
@@ -76,7 +106,8 @@ fn call_foo(x: Box<Trait>) {
 If only some methods aren't object-safe, you can add a `where Self: Sized` bound
 on them to mark them as explicitly unavailable to trait objects. The
 functionality will still be available to all other implementers, including
-`Box<Trait>` which is itself sized (assuming you `impl Trait for Box<Trait>`).
+`Box<dyn Trait>` which is itself sized (assuming you `impl Trait for Box<dyn
+Trait>`).
 
 ```
 trait Trait {
@@ -115,7 +146,9 @@ impl Trait for u8 {
 ```
 
 At compile time each implementation of `Trait` will produce a table containing
-the various methods (and other items) related to the implementation.
+the various methods (and other items) related to the implementation, which will
+be used as the virtual method table for a `dyn Trait` object derived from that
+implementation.
 
 This works fine, but when the method gains generic parameters, we can have a
 problem.
@@ -174,7 +207,7 @@ Now, if we have the following code:
 # impl Trait for u8 { fn foo<T>(&self, on: T) {} }
 # impl Trait for bool { fn foo<T>(&self, on: T) {} }
 # // etc.
-fn call_foo(thing: Box<Trait>) {
+fn call_foo(thing: Box<dyn Trait>) {
     thing.foo(true); // this could be any one of the 8 types above
     thing.foo(1);
     thing.foo("hello");
@@ -200,7 +233,7 @@ trait Trait {
 ```
 
 If this is not an option, consider replacing the type parameter with another
-trait object (e.g., if `T: OtherTrait`, use `on: Box<OtherTrait>`). If the
+trait object (e.g., if `T: OtherTrait`, use `on: Box<dyn OtherTrait>`). If the
 number of types you intend to feed to this method is limited, consider manually
 listing out the methods of different types.
 
@@ -226,7 +259,7 @@ trait Foo {
 }
 ```
 
-### The trait cannot contain associated constants
+### Trait contains associated constants
 
 Just like static functions, associated constants aren't stored on the method
 table. If the trait or any subtrait contain an associated constant, they cannot
@@ -248,7 +281,7 @@ trait Foo {
 }
 ```
 
-### The trait cannot use `Self` as a type parameter in the supertrait listing
+### Trait uses `Self` as a type parameter in the supertrait listing
 
 This is similar to the second sub-error, but subtler. It happens in situations
 like the following: