www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - How to make Rust trait like feature in dlang ?

reply lili <akozhao tencent.com> writes:
Hi, guys:
    I really like Rust trait feature. Use it can implement 
duck-type simple.
    base on inherited OO is complex and hard, because you need to 
design inherited tree well.
    and this is not easy. In real word need runtime polymorphic is 
uncommon. lots of is just need compile time polymorphic.

for example: design a network socket lib you need to implement 
TCP,UDP,Unix socket and these
sockets have need implement send, recv function.

interface SocketLike {
    int send(ubyte[] data, uint32 len);
    int recv(ubyte[] buff, uint32 len);
}

struct TcpSocket : SocketLike {...} // in dlang struct can not 
allows to inherited interface
struct UdpSocket : SocketLike {...}
struct UnixSocket : SocketLike {...}

function UseSocketLike(SocketLike socket)
{
    socket.recv()
     ....
}


I not need runtime polymorphic, just need compiler check real 
argument is SocketLike type.
How do this, can dlang support conception like feature.
Jun 16 2019
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Sunday, 16 June 2019 at 15:56:41 UTC, lili wrote:
 Hi, guys:
    I really like Rust trait feature. Use it can implement 
 duck-type simple.
    base on inherited OO is complex and hard, because you need 
 to design inherited tree well.
    and this is not easy. In real word need runtime polymorphic 
 is uncommon. lots of is just need compile time polymorphic.

 for example: design a network socket lib you need to implement 
 TCP,UDP,Unix socket and these
 sockets have need implement send, recv function.

 interface SocketLike {
    int send(ubyte[] data, uint32 len);
    int recv(ubyte[] buff, uint32 len);
 }

 struct TcpSocket : SocketLike {...} // in dlang struct can not 
 allows to inherited interface
 struct UdpSocket : SocketLike {...}
 struct UnixSocket : SocketLike {...}

 function UseSocketLike(SocketLike socket)
 {
    socket.recv()
     ....
 }


 I not need runtime polymorphic, just need compiler check real 
 argument is SocketLike type.
 How do this, can dlang support conception like feature.
Beyond the fact, that such questions should be placed in the learn forum section, you can achieve such checks via templating the function and usage of template constraints. https://dlang.org/spec/template.html#template_constraints For example, ´´´ import std; enum bool isSocketLike(T) = is(ReturnType!((T r) => r.send((ubyte[]).init, uint.init)) == int) && is(ReturnType!((T r) => r.recv((ubyte[]).init, uint.init)) == int); void main() { static assert(isSocketLike!T); static assert(!isSocketLike!F); UseSocketLike(T.init); static assert(!__traits(compiles, UseSocketLike(F.init))); } struct T { int send(ubyte[] data, uint l){return 0;} int recv(ubyte[] buff, uint l){return 0;} } struct F{} auto UseSocketLike(T)(T socket) if(isSocketLike!T) { // ... and so on ... } ´´´
Jun 16 2019
parent reply Newbie2019 <newbie2019 gmail.com> writes:
On Sunday, 16 June 2019 at 19:13:05 UTC, Alex wrote:
 Beyond the fact, that such questions should be placed in the 
 learn forum section, you can achieve such checks via templating 
 the function and usage of template constraints.

 https://dlang.org/spec/template.html#template_constraints

 For example,
 ´´´
 import std;

 enum bool isSocketLike(T) =
     is(ReturnType!((T r) => r.send((ubyte[]).init, uint.init)) 
 == int)
     && is(ReturnType!((T r) => r.recv((ubyte[]).init, 
 uint.init)) == int);

 void main()
 {
     static assert(isSocketLike!T);
     static assert(!isSocketLike!F);
     UseSocketLike(T.init);
     static assert(!__traits(compiles, UseSocketLike(F.init)));
 }

 struct T
 {
     int send(ubyte[] data, uint l){return 0;}
     int recv(ubyte[] buff, uint l){return 0;}
 }

 struct F{}

 auto UseSocketLike(T)(T socket) if(isSocketLike!T)
 {
     // ... and so on ...
 }
 ´´´
D can do it, but much less elegant. compare to Rust, d is like granny language.
Jun 16 2019
next sibling parent bauss <jj_1337 live.dk> writes:
On Monday, 17 June 2019 at 04:25:26 UTC, Newbie2019 wrote:
 On Sunday, 16 June 2019 at 19:13:05 UTC, Alex wrote:
 Beyond the fact, that such questions should be placed in the 
 learn forum section, you can achieve such checks via 
 templating the function and usage of template constraints.

 https://dlang.org/spec/template.html#template_constraints

 For example,
 ´´´
 import std;

 enum bool isSocketLike(T) =
     is(ReturnType!((T r) => r.send((ubyte[]).init, uint.init)) 
 == int)
     && is(ReturnType!((T r) => r.recv((ubyte[]).init, 
 uint.init)) == int);

 void main()
 {
     static assert(isSocketLike!T);
     static assert(!isSocketLike!F);
     UseSocketLike(T.init);
     static assert(!__traits(compiles, UseSocketLike(F.init)));
 }

 struct T
 {
     int send(ubyte[] data, uint l){return 0;}
     int recv(ubyte[] buff, uint l){return 0;}
 }

 struct F{}

 auto UseSocketLike(T)(T socket) if(isSocketLike!T)
 {
     // ... and so on ...
 }
 ´´´
D can do it, but much less elegant.
Maybe that's because they are not supposed to be implemented like that in D. Surprise, surprise.
 compare to Rust, d is like granny language.
Based on what merit?
Jun 17 2019
prev sibling next sibling parent Alex <sascha.orlov gmail.com> writes:
On Monday, 17 June 2019 at 04:25:26 UTC, Newbie2019 wrote:
 On Sunday, 16 June 2019 at 19:13:05 UTC, Alex wrote:
 Beyond the fact, that such questions should be placed in the 
 learn forum section, you can achieve such checks via 
 templating the function and usage of template constraints.

 https://dlang.org/spec/template.html#template_constraints

 For example,
 ´´´
 import std;

 enum bool isSocketLike(T) =
     is(ReturnType!((T r) => r.send((ubyte[]).init, uint.init)) 
 == int)
     && is(ReturnType!((T r) => r.recv((ubyte[]).init, 
 uint.init)) == int);

 void main()
 {
     static assert(isSocketLike!T);
     static assert(!isSocketLike!F);
     UseSocketLike(T.init);
     static assert(!__traits(compiles, UseSocketLike(F.init)));
 }

 struct T
 {
     int send(ubyte[] data, uint l){return 0;}
     int recv(ubyte[] buff, uint l){return 0;}
 }

 struct F{}

 auto UseSocketLike(T)(T socket) if(isSocketLike!T)
 {
     // ... and so on ...
 }
 ´´´
D can do it, but much less elegant. compare to Rust, d is like granny language.
I don't get your point. If you want a socket implementation, use (or use as an inspiration) an existent library, as Jacob mentioned. If you want some static checks of your code while developing, you could let away half of the code I showed. The compiler will check all interface usages anyway.
Jun 17 2019
prev sibling parent reply Mike Franklin <slavo5150 yahoo.com> writes:
On Monday, 17 June 2019 at 04:25:26 UTC, Newbie2019 wrote:

 D can do it, but much less elegant. compare to Rust, d is like 
 granny language.
Actually, D doesn't require all of the boilerplate to set up the interface and the implementation checking. You just type it up because the compiler will tell you when something's not right. ``` import std.stdio; struct TcpSocket { int send(ubyte[] data, int len) { writeln("TcpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("TcpSocket recv"); return len; } } struct UdpSocket { int send(ubyte[] data, int len) { writeln("UdpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UdpSocket recv"); return len; } } struct UnixSocket { int send(ubyte[] data, int len) { writeln("UdpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UdpSocket recv"); return len; } } struct NotASocket { } void Use(T)(T socket) { socket.send([], 0); socket.recv([], 0); } void main() { TcpSocket socket; socket.Use(); // Works out of the box without any interface boilerplate NotASocket noSocket; noSocket.Use(); // Error: no property `send`/`recv` for type `NotASocket` } ``` https://run.dlang.io/is/FpfkS6 I didn't even have to create an interface or implement one, yet the compiler knew what to do. If you really wanted to do the interface boilerplate like Rust, that's also possible in D, but unnecessary IMO. You could also use classes w/ interfaces, something Rust doesn't even have. So in this situation, D gives you everything Rust has and more with less boilerplate. Mike
Jun 17 2019
parent reply lili <akozhao tencent.com> writes:
On Monday, 17 June 2019 at 11:26:21 UTC, Mike Franklin wrote:
 On Monday, 17 June 2019 at 04:25:26 UTC, Newbie2019 wrote:

 D can do it, but much less elegant. compare to Rust, d is like 
 granny language.
Actually, D doesn't require all of the boilerplate to set up the interface and the implementation checking. You just type it up because the compiler will tell you when something's not right. ``` import std.stdio; struct TcpSocket { int send(ubyte[] data, int len) { writeln("TcpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("TcpSocket recv"); return len; } } struct UdpSocket { int send(ubyte[] data, int len) { writeln("UdpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UdpSocket recv"); return len; } } struct UnixSocket { int send(ubyte[] data, int len) { writeln("UdpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UdpSocket recv"); return len; } } struct NotASocket { } void Use(T)(T socket) { socket.send([], 0); socket.recv([], 0); } void main() { TcpSocket socket; socket.Use(); // Works out of the box without any interface boilerplate NotASocket noSocket; noSocket.Use(); // Error: no property `send`/`recv` for type `NotASocket` } ``` https://run.dlang.io/is/FpfkS6 I didn't even have to create an interface or implement one, yet the compiler knew what to do. If you really wanted to do the interface boilerplate like Rust, that's also possible in D, but unnecessary IMO. You could also use classes w/ interfaces, something Rust doesn't even have. So in this situation, D gives you everything Rust has and more with less boilerplate. Mike
Yes, It's ok. but this did not represent to an conception under code. I want to explicitly represent this is a SocketLike cenception. because i want conception oriented programming not type oriented, this is more powerful in GP. now I find a way that is use mixin template. ``` mixin template SocketLike { int send(ubyte[] data, int len); int recv(ubyte[] buff, int len); } struct TcpSocket { mixin SocketLike; //implement SocketLike behavior int send(ubyte[] data, int len) { writeln("TcpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("TcpSocket recv"); return len; } } struct UdpSocket { mixin SocketLike; //implement SocketLike behavior int send(ubyte[] data, int len) { writeln("TcpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("TcpSocket recv"); return len; } } ``` this not perfect not this is already abstract an SocketLike conception.
Jun 17 2019
parent reply Mike Franklin <slavo5150 yahoo.com> writes:
On Monday, 17 June 2019 at 12:11:19 UTC, lili wrote:

 Yes, It's ok. but this did not represent to an conception under 
 code.
 I want to explicitly represent this is a SocketLike cenception.
 because i want conception oriented programming not type 
 oriented, this is more powerful in GP.
It can still be done in D. It's just the `std.traits` module is missing the `implements` trait I've naively implemented below: ``` import std.stdio; import std.traits; template implements(T, I) { static foreach(m; __traits(allMembers, I)) { static assert( is(typeof(__traits(getMember, T, m)) == typeof(__traits(getMember, I, m))), "`" ~ T.stringof ~ "` does not implement `" ~ I.stringof ~ "." ~__traits(identifier, __traits(getMember, I, m)) ~ "`"); } enum implements = true; } interface ISocket { int send(ubyte[] data, int len); int recv(ubyte[] buff, int len); } struct TcpSocket { int send(ubyte[] data, int len) { writeln("TcpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("TcpSocket recv"); return len; } } struct UdpSocket { int send(ubyte[] data, int len) { writeln("UdpSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UdpSocket recv"); return len; } } struct UnixSocket { int send(ubyte[] data, int len) { writeln("UnixSocket send"); return len; } int recv(ubyte[] buff, int len) { writeln("UnixSocket recv"); return len; } } struct NotASocket { } void Use(T)(T socket) if (implements!(T, ISocket)) // ensures `T : ISocket` { socket.send([], 0); socket.recv([], 0); } void main() { TcpSocket socket; socket.Use(); NotASocket noSocket; noSocket.Use(); // Error: static assert: "NotASocket does not implement ISocket.send" } ``` https://run.dlang.io/is/KZcp1S There's probably a much more professional and elegant way to write `implements`, but you get the idea. It would probably be a worthwhile addition to the `std.traits` module for those that prefer it. Mike
Jun 17 2019
parent James Blachly <james.blachly gmail.com> writes:
On 6/17/19 9:17 AM, Mike Franklin wrote:

 There's probably a much more professional and elegant way to write 
 `implements`, but you get the idea.  It would probably be a worthwhile 
 addition to the `std.traits` module for those that prefer it.
 
 Mike
Ooh, that would be a nice addition to the standard library.
Jun 18 2019
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2019-06-16 17:56, lili wrote:
 Hi, guys:
     I really like Rust trait feature. Use it can implement duck-type 
 simple.
     base on inherited OO is complex and hard, because you need to design 
 inherited tree well.
     and this is not easy. In real word need runtime polymorphic is 
 uncommon. lots of is just need compile time polymorphic.
 
 for example: design a network socket lib you need to implement 
 TCP,UDP,Unix socket and these
 sockets have need implement send, recv function.
 
 interface SocketLike {
     int send(ubyte[] data, uint32 len);
     int recv(ubyte[] buff, uint32 len);
 }
 
 struct TcpSocket : SocketLike {...} // in dlang struct can not allows to 
 inherited interface
 struct UdpSocket : SocketLike {...}
 struct UnixSocket : SocketLike {...}
 
 function UseSocketLike(SocketLike socket)
 {
     socket.recv()
      ....
 }
 
 
 I not need runtime polymorphic, just need compiler check real argument 
 is SocketLike type.
 How do this, can dlang support conception like feature.
Have a look at how Mecca implements socket addresses [1]. [1] https://github.com/weka-io/mecca/blob/b36254e205ff8520effaa9ccc8bf74d73a28c639/src/mecca/lib/net.d#L538-L545 -- /Jacob Carlborg
Jun 16 2019
prev sibling next sibling parent reply Guillaume Piolat <contact+spam spam.org> writes:
On Sunday, 16 June 2019 at 15:56:41 UTC, lili wrote:
 How do this, can dlang support conception like feature.
Not only D has static polymorphism, but it does so with open capabilities / "DbI" / duck-typing-at-compile-time. A real eye opener is this talk: https://www.youtube.com/watch?v=LIb3L4vKZ7U Why do you think C++ took "static if"?
Jun 17 2019
parent reply lili <akozhao tencent.com> writes:
On Monday, 17 June 2019 at 09:23:35 UTC, Guillaume Piolat wrote:
 On Sunday, 16 June 2019 at 15:56:41 UTC, lili wrote:
 How do this, can dlang support conception like feature.
Not only D has static polymorphism, but it does so with open capabilities / "DbI" / duck-typing-at-compile-time. A real eye opener is this talk: https://www.youtube.com/watch?v=LIb3L4vKZ7U Why do you think C++ took "static if"?
Can't get your point. think what? C++ not support conception now. D not yet. But conception is really powerful. So is dlang will support it. for example: struct allow inherited interface, implement conception.
Jun 17 2019
parent reply XavierAP <n3minis-git yahoo.es> writes:
On Monday, 17 June 2019 at 10:25:57 UTC, lili wrote:
 Can't get your point. think what? C++ not support conception 
 now.
 D not yet. But conception is really powerful. So is dlang will 
 support it.
 for example: struct allow inherited interface, implement 
 conception.
I think you mean C++ "concepts"[1]?. These do exist in D (since much earlier); they are called template constraints[2]: struct TcpSocket { /*...*/ } struct UdpSocket { /*...*/ } // Template constraint: template isSocket(T) { import std.traits; enum bool isSocket = /*...*/ } auto UseSocket(ISocket)(ISocket sk) if(isSocket!ISocket) { /*...*/ } Still, template constraints or concepts work differently from structs as well as classes). But they can be not so different ways to accomplish the same. One difference is that D requires this "polymorphism" to be interfaces/traits. (Probably there is no reason why D could or should not add this capability, at least as long as it does not turn structs from values to references when assigned to an interface value.) This templated approach on the other hand is more flexible. Inheritance is not a sin as many people say nowadays, but it introduces rigidity in your design, and is not extensible for library users. _______ [1] https://en.wikipedia.org/wiki/Concepts_(C%2B%2B) [2] https://dlang.org/spec/template.html#template_constraints
Jun 17 2019
parent lili <akozhao tencent.com> writes:
On Monday, 17 June 2019 at 14:39:06 UTC, XavierAP wrote:
 On Monday, 17 June 2019 at 10:25:57 UTC, lili wrote:

 I think you mean C++ "concepts"[1]?. These do exist in D (since 
 much earlier); they are called template constraints[2]:

 One difference is that D requires this "polymorphism" to be 

 interfaces/traits. (Probably there is no reason why D could or 
 should not add this capability, at least as long as it does not 
 turn structs from values to references when assigned to an 
 interface value.)

 This templated approach on the other hand is more flexible. 
 Inheritance is not a sin as many people say nowadays, but it 
 introduces rigidity in your design, and is not extensible for 
 library users.
 _______
Thanks your answer。 I just start learning dlang few days.
Jun 17 2019
prev sibling parent Atila Neves <atila.neves gmail.com> writes:
On Sunday, 16 June 2019 at 15:56:41 UTC, lili wrote:
 Hi, guys:
    I really like Rust trait feature. Use it can implement 
 duck-type simple.
    base on inherited OO is complex and hard, because you need 
 to design inherited tree well.
    and this is not easy. In real word need runtime polymorphic 
 is uncommon. lots of is just need compile time polymorphic.

 [...]
https://github.com/atilaneves/concepts
Jun 18 2019