Repository navigation
object-safety and static methods #428
Description
Activity
I don't think having a default implementation in a constructor would be too common. At least, I have 2 use cases in hyper.
Here's one: I had to break up
NetworkStreamintoNetworkStreamandNetworkConnector. The Connector has one method:connect()that returns a NetworkStream. I originally had it as one trait.// ideally in the future trait NetworkStream { // 'constructor' fn connect(host, port, scheme) -> Self; // instance methods fn peer_name(&self) -> SocketAddr; fn set_timeout(&self); }
@seanmonstar why do you have
connecton the trait, rather than the concrete type? I'm trying to understand this corner of the design space a bit better. It seems in this case that connect might be better considered part of a NetworkStreamFactory abstraction, rather than a NetworkStream - is there a reason why not?Not sure if it's of as much interest to you guys, but I also have a use-case currently within my procedural music engine.
/// Generate a fill for the rhythm and append it to the end. fn gen_fill(&mut self, attr: &SongAttributes, part_dur: &Duration, phrase: &Phrase, generated: &Vec<Vec<&Rhythm>>, r: RhythmPack) { let mut temp: Self = Rhythm::empty(TimePack::from_attr(attr)); let fill_duration = self.gen_fill_duration(attr); temp.gen_section(attr, part_dur, fill_duration, phrase, generated, r); let perc = (self.get_ticks() - temp.get_ticks()) as f32 / self.get_ticks() as f32; self.truncate(perc); self.append(&temp); }
In saying this, I think I've managed to work out a way of moving the Rhythm trait into an enum instead, however it'll be a lot of work.
@mitchmindtree could you link to the traits and function definitions please? And explain how this fits with the static method issue? I don't understand what is going on, sorry.
@nick29581 my apologies, maybe I have the wrong idea of what's going on! I thought we were discussing use cases of static trait methods (that are constructor-like) in regards to your first option for dealing with object safety by disallowing them? Sorry if I've totally missed the mark here :)
edit: To clarify, I wouldn't be opposed to the decision, as I feel it would simply encourage me to use algebraic data types more often instead.
@nick29581 because each implementation of a
NetworkStreammay have some different way it connects, but I need the guarantee that the method will be there (compiler enforces this).impl Request { fn new(method: Method, url: Url) -> Request { Request::with_stream::<HttpStream>(method, url) } fn with_stream<N: NetworkStream>(method: Method, url: Url) -> Request { let stream: N = NetworkStream::connect(...); Request { body: box stream as Box<NetworkStream>, ... } } }
This gave me a few things:
- The possibility to create new types of streams, such as a
MockStreamto facilitate testing, and in the future, aHttp2Stream. Others can make their own as well if desired. - Boxing it lets the
Requesttype be more ergonomic: all methods can just acceptRequest, instead ofRequest<HttpStream>, which is more to type and means other streams can't work there, even though there shouldn't be any reason a higher level function would care.
Essentially
NetworkConnectordid become a factory trait, but besides making the compiler happy about object-safety, there was no desire to do so. It means implementors need to import a second trait. And havingconnect()on theNetworkStreamseemed just fine. It can be thought of asnew().- The possibility to create new types of streams, such as a
I think that if we stick with object safety (which at the moment I still prefer, though it's a fine line), then we should be consistent and hence consider "static fns" as making a trait not "object safe".
- added a commit that references this issue
on Dec 30, 2014 I see no other way than to forbid static methods and it turned out not to have much fallout, so we'll give it a go. Closing - tracking issue is rust-lang/rust#19949.
RFC #255 glossed over the issue of static methods. With the current implementation (plus rust-lang/rust#18527) we ignore static methods when deciding on object-safety (pre rust-lang/rust#18527, we ICE'ed). However, this does not allow us to use object-safety to guide the use of trait objects with traits (the 'auto-impl's for trait objects). E.g.,
Here, the call to
barshould be disallowed because the callX::foo()cannot be resolved becausefoois static.I see three options:
We could also relax the check on static methods where the method is provided rather than required, since there is then an implementation for a method when making a static method call. However, this would lose the usual, expected overriding behaviour (the provided method would always be called, even if the dynamic concrete type overrides it).
I think my favoured solution is option 1 with the provided method relaxation. I believe (though might be wrong here) that there is only benefit to having static constructor methods in a trait, rather than a concrete type, if there is a provided default method. So, the only cases which become inconvenient are where there is a provided constructor and this is overridden in a concrete type which is used to make an object.
See also discussion in rust-lang/rust#18527
cc @aturon @nikomatsakis