web-dev-qa-db-fra.com

Que signifie «Sized n'est pas implémenté»?

J'ai écrit le code suivant:

use std::io::{IoResult, Writer};
use std::io::stdio;

fn main() {
    let h = |&: w: &mut Writer| -> IoResult<()> {
        writeln!(w, "foo")
    };
    let _ = h.handle(&mut stdio::stdout());
}

trait Handler<W> where W: Writer {
    fn handle(&self, &mut W) -> IoResult<()>;
}

impl<W, F> Handler<W> for F
where W: Writer, F: Fn(&mut W) -> IoResult<()> {
    fn handle(&self, w: &mut W) -> IoResult<()> { (*self)(w) }
}

Et puis rustc dans mon terminal:

$ rustc writer_handler.rs
writer_handler.rs:8:15: 8:43 error: the trait `core::marker::Sized` is not implemented for the type `std::io::Writer`
writer_handler.rs:8     let _ = h.handle(&mut stdio::stdout());
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
writer_handler.rs:8:15: 8:43 error: the trait `core::marker::Sized` is not implemented for the type `std::io::Writer`
writer_handler.rs:8     let _ = h.handle(&mut stdio::stdout());
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pourquoi ce Writer est-il requis pour implémenter Sized? Il me semble que le Sized n'est pas nécessaire. Ce que je dois faire tout en gardant trait Handler pour avoir cet argument générique?


Dans Rust 1.0, ce code similaire produit le même problème:

use std::io::{self, Write};

fn main() {
    handle(&mut io::stdout());
}

fn handle(w: &mut Write) -> io::Result<()> {
    handler(w)
}

fn handler<W>(w: &mut W) -> io::Result<()>
where
    W: Write,
{
    writeln!(w, "foo")
}

Avec l'erreur:

error[E0277]: the trait bound `std::io::Write: std::marker::Sized` is not satisfied
 --> src/main.rs:8:5
  |
8 |     handler(w)
  |     ^^^^^^^ `std::io::Write` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `std::io::Write`
  = note: required by `handler`
35
k-ui

Le trait Sized est assez spécial, si spécial qu'il s'agit d'une limite par défaut sur les paramètres de type dans la plupart des situations. Il représente des valeurs qui ont une taille fixe connue au moment de la compilation, comme u8 (1 octet) ou &u32 (8 octets sur une plate-forme avec des pointeurs 64 bits) etc. Ces valeurs sont flexibles: ils peuvent être placés sur la pile et déplacés sur le tas, et généralement transmis par valeur, car le compilateur sait de combien d'espace il a besoin, où que la valeur aille.

Les types qui ne sont pas dimensionnés sont beaucoup plus restreints et une valeur de type Writer n'est pas dimensionnée: elle représente, de manière abstraite, un type non spécifié qui implémente Writer, sans savoir ce que le le type réel est. Comme le type réel n'est pas connu, la taille ne peut pas être connue: certains grands types sont Writers, certains petits types le sont. Writer est un exemple d'objet trait qui, pour le moment, ne peut apparaître que dans du code exécuté derrière un pointeur. Les exemples courants incluent &Writer, &mut Writer Ou Box<Writer>.

Cela explique pourquoi Sized est la valeur par défaut: c'est souvent ce que l'on veut.

Dans tous les cas, pour votre code, cela apparaît car vous utilisez handle avec h, qui est une Fn(&mut Writer) -> IoResult<()>. Si nous comparons cela avec le type F: Fn(&mut W) -> IoResult<()> qui Handle est implémenté car nous constatons que W = Writer, C'est-à-dire que nous essayons d'utiliser handle avec l'objet trait &mut Writer, pas un &mut W pour un type concret W. Ceci est illégal car les paramètres W dans le trait et l'impl ont par défaut une limite Sized, si nous le remplaçons manuellement avec ?Sized Alors tout fonctionne bien:

use std::io::{IoResult, Writer};
use std::io::stdio;

fn main() {
    let h = |&: w: &mut Writer| -> IoResult<()> {
        writeln!(w, "foo")
    };
    let _ = h.handle(&mut stdio::stdout());
}

trait Handler<W: ?Sized> where W: Writer {
    fn handle(&self, &mut W) -> IoResult<()>;
}

impl<W: ?Sized, F> Handler<W> for F
where W: Writer, F: Fn(&mut W) -> IoResult<()> {
    fn handle(&self, w: &mut W) -> IoResult<()> { (*self)(w) }
}

Et pour le code Rust 1.0:

use std::io::{self, Write};

fn main() {
    handle(&mut io::stdout());
}

fn handle(w: &mut Write) -> io::Result<()> {
    handler(w)
}

fn handler<W: ?Sized>(w: &mut W) -> io::Result<()>
where
    W: Write,
{
    writeln!(w, "foo")
}

J'ai également écrit un article de blog sur Sized et les objets de trait en général qui ont un peu plus de détails.

36
huon

Tout d'abord, notez que h est d'un type qui implémente Fn(&mut Writer) -> IoResult<()>.

h.handle Est appelé; cela dépend donc de l'implémentation de HandlerW est Writer— notez bien: W isWriter, an type non dimensionné. La &mut stdio::stdout() va donc être transtypée en l'objet trait &mut Writer. Tout cela est très bien en théorie, mais une fois ramené à la mise en œuvre tombe en panne. En ce qui concerne les contraintes, elles sont dimensionnées par défaut, et il se plaint donc que Writer, la valeur que vous essayez d'affecter pour W, n'est pas dimensionnée.

Il existe deux solutions principales ici:

  1. Basculez vers l'utilisation du type d'écrivain concret sur h afin de traiter un type de taille:

    use std::io::{IoResult, Writer, stdio, LineBufferedWriter};
    use std::io::stdio::StdWriter;
    
    fn main() {
        let h = |&: w: &mut LineBufferedWriter<StdWriter>| -> IoResult<()> {
            writeln!(w, "foo")
        };
        let _ = h.handle(&mut stdio::stdout());
    }
    
    trait Handler<W> where W: Writer {
        fn handle(&self, &mut W) -> IoResult<()>;
    }
    
    impl<W, F> Handler<W> for F
    where W: Writer, F: Fn(&mut W) -> IoResult<()> {
        fn handle(&self, w: &mut W) -> IoResult<()> { (*self)(w) }
    }
    
  2. Autorisez W à être un type non dimensionné. Ceci est acceptable car vous ne l'utilisez que par le biais d'une référence &mut W. Si vous souhaitez l'utiliser comme type nu, par ex. une méthode prenant W par valeur, elle ne le ferait pas.

    use std::io::{IoResult, Writer};
    use std::io::stdio;
    
    fn main() {
        let h = |&: w: &mut Writer| -> IoResult<()> {
            writeln!(w, "foo")
        };
        let _ = h.handle(&mut stdio::stdout());
    }
    
    trait Handler<W: ?Sized> where W: Writer {
        fn handle(&self, &mut W) -> IoResult<()>;
    }
    
    impl<W: ?Sized, F> Handler<W> for F
    where W: Writer, F: Fn(&mut W) -> IoResult<()> {
        fn handle(&self, w: &mut W) -> IoResult<()> { (*self)(w) }
    }
    

Je recommanderais de prendre en charge un W non dimensionné même si vous ne l'utilisez pas dans ce cas — il n'y a aucune raison qu'il a besoin pour être dimensionné.

5
Chris Morgan