Skip to content

Commit 950c2d8

Browse files
committed
Add huge explanation for E0038 (object safety)
1 parent 37a84bc commit 950c2d8

File tree

1 file changed

+250
-1
lines changed

1 file changed

+250
-1
lines changed

src/librustc/diagnostics.rs

Lines changed: 250 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,256 @@ match 5u32 {
428428
```
429429
"##,
430430

431+
E0038: r####"
432+
433+
Trait objects like `Box<Trait>`, can only be constructed when certain
434+
requirements are obeyed by the trait in question.
435+
436+
Trait objects are a form of dynamic dispatch and use dynamically sized types.
437+
So, for a given trait `Trait`, when `Trait` is treated as a type, as in
438+
`Box<Trait>`, the inner type is "unsized". In such cases the boxed pointer is a
439+
"fat pointer" and contains an extra pointer to a method table for dynamic
440+
dispatch. This design mandates some restrictions on the types of traits that are
441+
allowed to be used in trait objects, which are collectively termed as "object
442+
safety" rules.
443+
444+
Attempting to create a trait object for a non object-safe trait will trigger
445+
this error.
446+
447+
448+
There are various rules:
449+
450+
### The trait cannot require `Self: Sized`
451+
452+
When `Trait` is treated as a type, the type does not implement the special
453+
`Sized` trait, because the type does not have a known size at compile time and
454+
can only be accessed behind a pointer. Thus, if we have a trait like the
455+
following:
456+
457+
```
458+
trait Foo where Self: Sized {
459+
460+
}
461+
```
462+
463+
we cannot create an object of type `Box<Foo>` or `&Foo` since in this case
464+
`Self` would not be `Sized`.
465+
466+
Generally `Self : Sized` is used to indicate that the trait should not be used
467+
as a trait object. If the trait comes from your own crate, consider removing
468+
this restriction.
469+
470+
### Method references the `Self` type in its arguments or return type
471+
472+
This happens when a trait has a method like the following:
473+
474+
```
475+
trait Trait {
476+
fn foo(&self) -> Self;
477+
}
478+
impl Trait for String {
479+
fn foo(&self) -> Self {
480+
"hi".to_owned()
481+
}
482+
}
483+
484+
impl Trait for u8 {
485+
fn foo(&self) -> Self {
486+
1
487+
}
488+
}
489+
```
490+
491+
In such a case, the compiler cannot predict the return type of `foo()` in a case
492+
like the following:
493+
494+
```
495+
fn call_foo(x: Box<Trait>) {
496+
let y = x.foo(); // What type is y?
497+
// ...
498+
}
499+
```
500+
501+
If the offending method isn't actually being called on the trait object, you can
502+
add a `where Self: Sized` bound on the method:
503+
504+
```
505+
trait Trait {
506+
fn foo(&self) -> Self where Self: Sized;
507+
// more functions
508+
}
509+
```
510+
511+
Now, `foo()` can no longer be called on the trait object, but you will be
512+
allowed to call other trait methods and construct the trait objects. With such a
513+
bound, one can still call `foo()` on types implementing that trait that aren't
514+
behind trait objects.
515+
516+
### Method has generic type parameters
517+
518+
As mentioned before, trait objects contain pointers to method tables. So, if we
519+
have
520+
521+
```
522+
trait Trait {
523+
fn foo(&self);
524+
}
525+
impl Trait for String {
526+
fn foo(&self) {
527+
// implementation 1
528+
}
529+
}
530+
impl Trait for u8 {
531+
fn foo(&self) {
532+
// implementation 2
533+
}
534+
}
535+
// ...
536+
```
537+
538+
at compile time a table of all implementations of `Trait`, containing pointers
539+
to the implementation of `foo()` would be generated.
540+
541+
This works fine, but when we the method gains generic parameters, we can have a
542+
problem.
543+
544+
Usually, generic parameters get _monomorphized_. For example, if I have
545+
546+
```
547+
fn foo<T>(x: T) {
548+
// ...
549+
}
550+
```
551+
552+
the machine code for `foo::<u8>()`, `foo::<bool>()`, `foo::<String>()`, or any
553+
other type substitution is different. Hence the compiler generates the
554+
implementation on-demand. If you call `foo()` with a `bool` parameter, the
555+
compiler will only generate code for `foo::<bool>()`. When we have additional
556+
type parameters, the number of monomorphized implementations the compiler
557+
generates does not grow drastically, since the compiler will only generate an
558+
implementation if the function is called with hard substitutions.
559+
560+
However, with trait objects we have to make a table containing _every object
561+
that implements the trait_. Now, if it has type parameters, we need to add
562+
implementations for every type that implements the trait, bloating the table
563+
quickly.
564+
565+
For example, with
566+
567+
```
568+
trait Trait {
569+
fn foo<T>(&self, on: T);
570+
// more methods
571+
}
572+
impl Trait for String {
573+
fn foo<T>(&self, on: T) {
574+
// implementation 1
575+
}
576+
}
577+
impl Trait for u8 {
578+
fn foo<T>(&self, on: T) {
579+
// implementation 2
580+
}
581+
}
582+
// 8 more implementations
583+
```
584+
585+
Now, if I have the following code:
586+
587+
```
588+
fn call_foo(thing: Box<Trait>) {
589+
thing.foo(true); // this could be any one of the 8 types above
590+
thing.foo(1);
591+
thing.foo("hello");
592+
}
593+
```
594+
595+
we don't just need to create a table of all implementations of all methods of
596+
`Trait`, we need to create a table of all implementations of `foo()`, _for each
597+
different type fed to `foo()`_. In this case this turns out to be (10 types
598+
implementing `Trait`)*(3 types being fed to `foo()`) = 30 implementations!
599+
600+
With real world traits these numbers can grow drastically.
601+
602+
To fix this, it is suggested to use a `where Self: Sized` bound similar to the
603+
fix for the sub-error above if you do not intend to call the method with type
604+
parameters:
605+
606+
```
607+
trait Trait {
608+
fn foo<T>(&self, on: T) where Self: Sized;
609+
// more methods
610+
}
611+
```
612+
613+
If this is not an option, consider replacing the type parameter with another
614+
trait object (e.g. if `T: OtherTrait`, use `on: Box<OtherTrait>`). If the number
615+
of types you intend to feed to this method is limited, consider manually listing
616+
out the methods of different types.
617+
618+
### Method has no receiver
619+
620+
Methods that do not take a `self` parameter can't be called since there won't be
621+
a way to get a pointer to the method table for them
622+
623+
```
624+
trait Foo {
625+
fn foo() -> u8;
626+
}
627+
```
628+
629+
This could be called as `<Foo as Foo>::foo()`, which would not be able to pick
630+
an implementation.
631+
632+
Adding a `Self: Sized` bound to these methods will generally make this compile.
633+
634+
635+
```
636+
trait Foo {
637+
fn foo() -> u8 where Self: Sized;
638+
}
639+
```
640+
641+
### The trait cannot use `Self` as a type parameter in the supertrait listing
642+
643+
This is similar to the second sub-error, but subtler. It happens in situations
644+
like the following:
645+
646+
```
647+
trait Super<A> {}
648+
649+
trait Trait: Super<Self> {
650+
}
651+
652+
struct Foo;
653+
654+
impl Super<Foo> for Foo{}
655+
656+
impl Trait for Foo {}
657+
```
658+
659+
Here, the supertrait might have methods as follows:
660+
661+
```
662+
trait Super<A> {
663+
fn get_a(&self) -> A; // note that this is object safe!
664+
}
665+
```
666+
667+
If the trait `Foo` was deriving from something like `Super<String>` or
668+
`Super<T>` (where `Foo` itself is `Foo<T>`), this is okay, because given a type
669+
`get_a()` will definitely return an object of that type.
670+
671+
However, if it derives from `Super<Self>`, the method `get_a()` would return an
672+
object of unknown type when called on the function, _even though `Super` is
673+
object safe_. `Self` type parameters let us make object safe traits no longer
674+
safe, so they are forbidden when specifying supertraits.
675+
676+
There's no easy fix for this, generally code will need to be refactored so that
677+
you no longer need to derive from `Super<Self>`.
678+
679+
"####,
680+
431681
E0079: r##"
432682
Enum variants which contain no data can be given a custom integer
433683
representation. This error indicates that the value provided is not an
@@ -1295,7 +1545,6 @@ contain references (with a maximum lifetime of `'a`).
12951545

12961546
register_diagnostics! {
12971547
// E0006 // merged with E0005
1298-
E0038,
12991548
// E0134,
13001549
// E0135,
13011550
E0136,

0 commit comments

Comments
 (0)