I can't wait the day when, we can avoid crazy long errors like this [2], which ends with a cryptic error "not match for operator + in iterator<foo<bar<.......> > >::ref " we will get clear, english errors that can be defined by the library writers : "The items in this collection needs to be summable" or "This collection must be sortable" , etc. etc.
[1] http://honermann.net/blog/2016/03/06/why-concepts-didnt-make...
[2] https://www.codeproject.com/KB/cpp/791461/errors_temp.PNG
ahem.
One huge difference in general between C++ templates and Rust's generics (I _believe_ Java/C# are more like Rust here, and I'm not sure about Swift) in that C++ templates generally don't check that their calling code actually supports the type that's passed into the template. In other words, the check happens after the template is expanded. (This is the sort example on page 3). Rust's generics system does the opposite: it requires that if you call a method on some generic parameter, that generic parameter is constrained by a trait that has that method. This happens before expansion. So concepts are, in some ways, an attempt to make the C++ system closer to Rust's system. (This is ignoring the actual chronology here, of course: concepts have been in development a long time).
The way "requires" works here is very different than in Rust, as well. In Rust, you define a trait the same way you'd define regular methods. If those methods have a body, then it's used as a default implementation, if it does not, then you must implement that method. The equivalent of "must have an iterator type" is associated types, you add "type Name;" instead of a method signature.
So the equivalent of the "Sequence" type on page 7 would be
trait Sequence {
type Value;
type Iterator<Item=Value>; // Iterator itself has an associated type, Value
fn begin() -> Self::Iterator;
fn end() -> Self::Iterator;
}
roughly speaking. I don't think this exactly works, but just to give you some idea of how the syntaxes roughly compare. In general, I know that a Sequence trait in Rust would require higher kinded types or associated type constructors, which Rust does not yet have, and so the concept is probably not directly expressible.That's my impressions after reading this. I have been meaning to read about concepts for a while, but since I only have so much time, have mostly waited to see how they shake out before really digging in. It's slightly slower going since my C++ isn't spectacular. This post mostly represents my understanding of the differences, I'm not trying to make any judgements here, etc.
If I understand correctly, placeholders may work for this. This use is described in the "Programming with placeholders" section of the "Introducing concepts" article: https://accu.org/index.php/journals/2157
The remaining articles in Andrew Sutton's C++ concepts series are pretty good, too; so, just in case anyone is interested, here are the links:
- "Defining Concepts" - https://accu.org/index.php/journals/2198
- "Overloading with Concepts" - https://accu.org/index.php/journals/2316
The name has been used in the C++ community since at last mid 90s as it originated from Alex Stephanov work on the STL. Stephanov himself might have used the name even earlier than that. Formalization of concepts which has been attempted at least since the early 00s was influenced by Haskell and in turn did influence both Rust and Swift.
I feel that lazy ownership gets really gnarly when you start associating large native resources with objects and then can't clearly be sure who's owning a reference to what(see Activity/Context leaking in Android).
All of this stuff is solveable with proper diligence but I prefer my language to enforce it.
This is why I love Rust in a nutshell (okay, there are actually a lot of reasons, but this is a big one). Given the choice between my ability to write perfectly bug-free code and the compiler to make sure that my code is correct, I'll pick the compiler any day of the week. And that's before taking into account the fact that like most programmers, I have to work with code that's not mine as well.
Exactly. Everyone trying to defend C always makes this argument, well you just need perfect programmers who write code without mistakes! Why can't everybody just write good C? But some of us live in the real world where those mistakes mean huge vulns.
(Not hating on C in general, just in any security-sensitive context)
Have you considered forking Go and replacing the type system? If such an experiment was successful this could be the fabled Go 2.0...
That is, it is resolved at compilation time and not at run-time as these that you cited except for some cases in Rust where Traits can be both run-time or compile-time depending on how you use them.
For decades C++ has the equivalent of these, it is called virtual pure classes (we may even consider virtual in general).
But those cannot really fulfill the role of concepts in templates.
Kind of like EvilML maybe: https://github.com/akabe/evilml
Everything is generic in ML but it's not clear what runtime overhead there is. The compiler does so many transformations that it's hard to reason about.
trait Foo { } // pretend this has methods
fn static_dispatch<T: Foo>(x: &T) {}
fn dynamic_dispatch(x: &Foo) {}
One area in which dynamic dispatch works differently in Rust is that &Foo there is a "double pointer": it's a (pointer to data, pointer to vtable), which is different than how C++ does it. The static dispatch code will get monomorphized for each T that it's called with. fn dynamic_dispatch(x: &virtual Foo) { }
fn static_dispatch(x: &Foo) { }
// is just syntactic sugar for
fn static_disatch<T: Foo>(x: &T) { }
As "consolation," dynamic dispatch is usually harder to wrangle with for semantic reasons and so people generally don't use it unless they need to.For example in the Number concept, can you have a PositiveNumber concept (e.g., require Number > 0)? Or given some container, that it is sorted? Or if you have a binary operator, that it is associative?
Maybe with some magical compiler that can tell you that after calling abs() or exp() the "double" type becomes a positive number concept, or that after calling sort on a vector and only doing binary inserts that the vector is still sorted.
Basically a lot of contracts on how to use a function correctly is not captured by just type system or whether you can call certain functions on a type. Is there an easy way to attach a "concept" to an output in an ad hoc way?
There's also Idris, which I haven't really used, but from what I understand, it's sort of like Haskell with dependent types.
there's already work like this, for instance dafny [1], where you can have functions like this:
method Abs(x: int) returns (y: int)
ensures 0 <= y
{
...
}
or: method MultipleReturns(x: int, y: int) returns (more: int, less: int)
requires 0 < y
ensures less < x < more
{
more := x + y;
less := x - y;
}
[1] https://www.microsoft.com/en-us/research/project/dafny-a-lan...LiquidHaskell is nice because it's all written in comments next to or in a separate file from your Haskell code. This means the Haskell compiler doesn't even have to know about it. On the other hand, when you're using something like Idris for formal programming, you often find your program full of proof witnesses that you aren't actually using for your real code you want to write. You can prove all sorts of things, like that a function only returns negative multiples of 3, or only returns vectors of length 5n+1.
The latest proposal is P0380, "A Contract Design": https://wg21.link/P0380.
An earlier work, "Simple Contracts for C++" (https://wg21.link/P0287), gives some further background and motivation.
Notably, contracts are currently solely in the proposal stage (and not in the upcoming standard).
The current concept proposal is significantly reduced in scope and doesn't include anything like axioms.
It isn't as clean, but it does the job.
For example you could have a PositiveNumber class, with the mathematical operators overloaded that always validated the invariant of the number being positive.
- Weird syntax, the requires thing. I should be able to write a "static interface" in much the same way that I can write pure virtual functions, that is a bunch of functions.
- Ideally one could write the same "interface" and use it either statically (just checking a specific type) or dynamically (vtable calls).
- It is not verified at compile time that the code is using ONLY the defined interface and not something else accidentally.
I really think C++ should go toward something like Rust traits, which work pretty much like what I described above.
Actually in C# interfaces and generics work similarly, save for the fact that they cannot generally do static dispatch, just static verification. In a C# generic method it is not possible to use features of a generic type other than those implied by the "where" constraints.
EDIT: Here's my quick hacky "implementation" of static interfaces in C++. http://ideone.com/yPaZ17