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`
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 Writer
s, 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.
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 Handler
où W
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:
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) }
}
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é.