Everything in moderation of course. For me personally simplicity of go is too much and I don't feel comfortable writing it.
Everything in moderation of course. For me personally simplicity of go is too much and I don't feel comfortable writing it.
However, Go's poor type system also forces you to write worse and less safe code than you want.
For example, the lack of enums and sum types means it's hard to represent, safely and efficiently, anything that should be the same type but have different values within a strict set of allowed values. The nearest you get in Go to an enum is:
type Color int
const (
Red Color = iota
Blue
Green
)
Alternatively, can do the same with a string: type Color string
const Red Color = "red"
// etc.
This gives you some type safety, since a Color requires casting to force an int or string or whatever to become it. But that doesn't give you real safety, since you can do: var c Color = Color("fnord")
This comes up whenever you're handling foreign input. JSON unmarshaling, for example. You can override the unmarshalling to add some validation, but it won't solve your other cases; it will always be possible to accidentally accept something bad. Not to mention that the "zero value" of such a value is usually wrong: type Color string
var c color // Empty string, not valid!
A more safe way is to hide the value: type Color interface {
isColor()
}
type colorValue string
func (colorValue) isColor() {}
var Red Color = colorValue("red") // etc.
Now nobody can create an invalid value, and the zero value is nil, not an invalid enum value. But this is unnecessarily complicated and shouldn't be necessary in a modern language in 2019.The case of genuine sum types is worse. Your best bet is to use sealed interfaces:
type Action interface {
isAction()
}
type TurnLeft struct{}
func (TurnLeft) isAction() {}
type MoveForward struct{
Steps int
}
func (MoveForward) isAction() {}
There are some downsides. You have to use static analysis tools (go-sumtype is good) to make sure your type switches are exhaustive. You get a performance penalty from having to wrap all values in an interface. And if you're going to serialize or deserialize this (e.g. JSON), you will be writing a whole bunch of logic to read and write such "polymorphic" values.It’s not a pretty solution from a language design point of view, but it’s been more than effective for us from an engineering point of view: since we’d need those proto definitions anyway, why bother writing our own?
Like most systems guys, they know of how C handles types and how C++ handles types. The concept of Parametric Polymorphism or sum types outside of OOP is most likely something Rob Pike was not familiar with as it's hard to see why sum types were not included in the language.
Having IO functions return a tuple of error and value rather then an Option type is not simplicity it's complexity arising from lack of a type primitive. The feature is ugly and very much looks like it was implemented by someone who wasn't aware of a type that can be a Value OR an Error. So instead he implemented a type that can be an Error AND a Value and left it to manual run time checks for the programmer to figure out if an error even occurred.
The other thing is that this "tuple" type of error AND value looks like a hack. Tuple types can't be saved to a variable in GO and can only be returned by functions and immediately unrolled into its separate primitive values. It's like Rob knew something was off in this area so he created a temporary concept of a tuple in the return value of a function to make up for it. A consistently designed language wouldn't have a Tuple only returnable by a function call. It seems strange and inconsistent.
Additionally, the fact that, in GO, some types can have Nils and other types default to a zero value implies that Rob Knew nulls were bad but didn't know how to completely get rid of the null.
I'm thinking that Robs initial notions of sum types and parametric polymorphism is that they can only be implemented via hierarchies of inheritance which in itself has many problems. It makes sense because this is what systems programmers are exposed to (C, C++) as opposed to typed lambda calculus or Haskell. So it's easy to see that Go is the result of Robs awareness of problems with OOP but lack of awareness of the theoretical alternative.
First, it's often possible for the function to return a meaningful value even in an error case (e.g., number of bytes read before the error occurred).
Second, it's often possible to return a sensible 'null' value together with an error which can be handled correctly without checking the error value. (A map lookup is the obvious example of this.) This simplifies logic in some places.
Using sum types for errors in Go wouldn't actually work very well unless you fundamentally changed other aspects of the language. You'd need pattern matching, a whole bunch of generic higher order functions for manipulating option/result types, etc. etc.
Create a type that explicitly stores this information. The return type can hold the (error message and a value) OR (just a value). This type expression is a more accurate description of what's really going on. Whichever way you want to represent the product type it's not isomorphic to the actual intended result that the function should return. A sum type can represent the return value of the sentence below while GO cannot:
"A function that returns (a value) OR an (error with a value)"
This is the true intention of the function you described.
>Second, it's often possible to return a sensible 'null' value together with an error which can be handled correctly without checking the error value. (A map lookup is the obvious example of this.) This simplifies logic in some places.
But opens up the possibility of a runtime error if you fail to check for it. Historically there are tons of functions in C, C++ or javascript that use null to represent an error and it is self quoted to be the greatest mistake ever made by the creator of null. No language needs a null.
>Using sum types for errors in Go wouldn't actually work very well unless you fundamentally changed other aspects of the language. You'd need pattern matching....
Using product types to represent errors has already changed the nature of GO in a very hacky way.
Only Functions in GO can return tuples and the concept of the tuple can never be used anywhere else. You cannot save a variable as a tuple, you cannot pass a tuple as an argument. You can only return a tuple then instantly unroll it. It's an arbitrary hacky feature obviously made to support error values.
It would be better to have an arbitrary pattern matching feature... this makes more sense then arbitrary tuples returned from functions.
>a whole bunch of generic higher order functions for manipulating option/result types, etc. etc.
Actually no you don't. The fact that go functions return tuples with errors, does this mean that higher order functions need to handle tuples? No! not at all. In fact go explicitly eliminates support for this... The tuples in GO need to be unrolled into their constituent types before they can be used in any other function. The same concept can be applied to Option types. You have to unroll the value and explicitly handle either individual type. You do not ever need a higher order function that accepts the Option type as a parameter.
Like all languages that have the Option Type/Maybe Monad etc... Any function that returns this type represents a function that is impure that needs to be unrolled first before passing the value down to the functions that do closed and pure calculations. A function that takes an Option type as a parameter is a function that says "I am a function that can only take values from IO" It's very rare for functions to be implemented like this even in languages that have first class support for the sum type and Monads. In haskell I can't recall ever seeing a function that takes the IO monad as a parameter. In haskell and in Rust these values need to be unrolled into their constituent types before they can be used.
Please note I am not advocating the inclusion of Monads into GO. Just talking about sum types.
The fact is that Rob almost certainly knows more than you, rather than less. And he still made the choices he made. That should make you ask questions, not about Rob's knowledge, but about yours.
1. Rob Pike spent all that time at Bell Labs, with all these CS experts, read and wrote all those papers, and never heard about sum types. That's... possible. It's not the way I would bet, but it's possible.
2. Rob Pike knew perfectly well what sum types were, and left them out of Go, because he thought they didn't fit with what he was trying to do.
To me, the second is both more charitable, and more in line with what I think Rob Pike's background and experience would have exposed him to. crimsonalucard obviously disagrees. He seems to think that sum types are so obviously the right thing that Pike could not have possibly not put them in Go had he known about them, and therefore he could not have known. And that is in fact possible.
But it seems to me to better fit with Pike's background, as well as with the principle of charity, to assume that he knew. And still he chose to leave them out.
Now, he could still be wrong. And we can discuss whether sum types are really a good fit for what Go is trying to do. But the assumption that he couldn't have known, or he would have done it the way someone else thinks he should have, is what grates on me.
For what it's worth, the Go FAQ (at https://golang.org/doc/faq#variant_types) says that they considered sum types, and didn't think they fit.
Please read my initial assumption. In no place did I say he COULDN'T have known. Read it. I literally started the statement with "I think the designers probably didn't know" rather than "I know they COULDN'T have known." There is nothing to "grate" you here. I simply had an opinion and a guess, and you disagreed with it and decided to insult me.
What grates me is the assumption that I said it's 100% true that Rob Pike didn't know what a sum type was. I think it's very likely he didn't know. If he did know then I am wrong. That's all.
>For what it's worth, the Go FAQ (at https://golang.org/doc/faq#variant_types) says that they considered sum types, and didn't think they fit.
That FAQ should have been presented earlier in a cordial and civil way. If you did I would have admitted that my hypothesis was incorrect. Science logic and evidence rule the day and I try to not invest any emotion into any of my opinions. It's hard but I follow this rule. If the FAQ says he knows about it then he does and I am wrong. Instead you chose not to present this evidence and call me arrogant.
There was no need to call me "Arrogant." It disgusts me to hear people talk like this. Either way the GO the language feels awkward in the way it uses product types and does indeed feel like Rob didn't know about them because the sum types certainly do feel more fitting then having a function return a tuple out of nowhere.
I also disagree with the FAQ. Plenty of languages have constraint types that are placed on the subtypes of the sum type. There's no confusion imo. Also note that the previous sentence was just an opinion. Please don't call me arrogant because I have one.
And I don't see the FAQ as necessarily total vindication of my position. The language team considered sum types; it doesn't mean that Rob Pike did in the initial design. It could be that, after it was kind of mostly formed, they thought about sum types and couldn't find a sensible way to make them fit. Or it could mean that he considered them and rejected them from the beginning. The FAQ isn't specific enough to say.
As for calling you arrogant: You are not the first person who has said, here on HN, that Pike "looked like he didn't know"/"must not have known"/"couldn't have known". Those conversations kind of run together in my mind. As a result, I was hard on you at least in part because others went too far. That's not fair to you, and I apologize.
I also cannot call you arrogant for having an opinion. I also have one - you may have noticed this. ;-)
However, I feel that I should say (and say as gently as I can) that you often sound very harsh on HN. A harsh tone causes many to read your content with less charity than the ideas might deserve. (I am not here trying to defend my interaction in this thread.) And this is not very helpful of me, because if you ask for advise on what, specifically, to change, I'm not sure I can give any. I mention it because you may be unaware of it, and awareness may help.
I can easily see how the previous paragraph could offend you. I am not trying to do so. Forgive me if it causes offense.
I have been using the Wirth languages a lot (Pascal, Modula), and one of the big appeals of Go to me is, that it brings back a lot from those languages to the modern times. The Wirth languages are far to underrated in programming today.
When Go came out, I though it could follow Oberon, starting small and eventually reach Active Oberon/Zonnon expressiveness, but alas that is not how they see it.
Even Limbo has features that Go still lacks.
More charitable to Rob Pike, rather than to the person you're in the the middle of a conversation with.
Anyway "appeal to authority" is not an argument, it's a religion. Our lord and savior, Rob, knows so much that his design decisions are beyond question.
Read that quote by Rob and the responses. IN the quote rob describes what he believes Generic Types are at face value... he literally takes it into a tirade about inheritance and hierarchies of classes... something that is not part of type theory at all.
Honestly, it feels like he didn't know about Algebraic Data types. I'm not the only one who thinks this as shown from the responses to his quotation.
One of the responses:
"Or perhaps Rob Pike just hasn't explored the relevant literature in enough depth. At one point he admitted that he didn't know that structural typing had already been invented previously! This isn't to criticize Rob, I find his talks fascinating, I think he's awesome, he's a friend of my boss, etc. But he's hardly the first hard-core hacker to be ignorant of the degree to which type theory has seen dramatic advances since the 1980s."
So, at the point they were creating Go, I think it's perfectly reasonable they had even less exposure to fp, and didn't actually know about these better solutions.
Firstly, "enums" using iota should always be defined as either
const (
Red Colour = iota + 1
Blue
Green
)
or const (
Unknown Colour = iota
Red
Green
Blue
)
to avoid the zero value problem.Secondly, and this is a personal preference, I've really enjoyed not having to work with sum types. In practice other programmers seem to use them when a public interface would have been sufficient, and it's convenient to be able to do:
// original package
type Position struct {
X, Y int
Rot Rotation
}
type Action interface {
Apply(Position)
}
type MoveForward struct {
Steps int
}
func (m MoveForward) Apply(p Position) {
switch p.Rot {
case Up:
p.Y += m.Steps
...
}
}
// second package wrapping the first
type WarpToPoint struct {
X, Y int
}
func (w WarpToPoint) Apply(p movement.Position) {
p.X, p.Y = w.X, w.Y
}IME it’s useful to explicitly define the zero value’s meaning as “UNSPECIFIED”, to simplify the problem of trying to guess if the client intended to pass a zero value.
Because there's not actually support for enums in go.
There's support for constant values, and automatically assigning sequential values to them. That happens to be useful for solving the same kinds of problems that enums solve, but they're not equivalent.
Your example of what you prefer uses (a shitty approximation of) a sum type in p.Rot.
(This is also the most basic possible use of a sum type; they are not only useful for enums, it's just to point out that even a large amount of "simple" Go code would benefit from them.)
I want to reiterate that I am aware sum types can be useful. I just don't think they're useful _enough_ to outweigh being a footgun for calling code.
For example, consider an AST for a programming language. AST nodes don't have any behaviour (though they might have some methods for convenience, though nothing that implements behaviour). You want to do optimizations and printing and compilation and so on, but on top of the pure AST.
That's usually sufficient. Any "cheating" should come up in code review as a suspicious cast to Color. In an audit, you could search for casts to Color.
Safe languages often have unsafe constructs. It's the same principle. The unsafe code is signposted, and you review it.
If you want further encapsulation, another useful trick is to make Color a struct with a private field. It's not usually necessary, though.
Go does have an unfortunate quirk that you can always create a zero value without calling a constructor, so you'll need to make sure a zero Color has meaning. (An interface doesn't really change this because the then the zero value is nil. That's not an improvement over making the zero value mean "black" or "transparent" or "invalid".)
type Color struct {
R, G, B, A byte // IDK
}
func (o OtherType) *Color {
return &Color{R: o.r, B: o.b, G: o.g}
}
type Colorer interface {
func Color() *Color
}
A colorer would return a color regardless of its type. This is behavior-based interface. I just need a thing that when I call Color(), you get a *Color.github.com/go-playground/validator/v10
It uses struct tags to validate the struct and is quite extensive.
"I want to interact with a REST API and pull a field out of its response JSON" is an incredibly common workflow, and yet to do that in golang is far from trivial. You need to define serializer types and all sorts of stuff (or you can take a route I've seen encouraged where people to use empty interfaces, which can cause runtime exceptions).
Same deal with a worker pool. Concurrency is great, but instead of providing a robust, well written solution as part of the language itself, it gives you a toy like this https://gobyexample.com/worker-pools (still the most common result on Google) that is only 80% of the way there. Then you find yourself bolting things onto it to cover your features (we need to know if things fail, so let's just add another channel. We also need finality, whelp, another channel it is), and before you know if you have an incomprehensible mess.
// Interact with a REST API and pull a field out of its response JSON.
func interact(url string) (field string, err error) {
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("error making HTTP request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("error querying API: %d %s", resp.StatusCode, resp.Status)
}
var response struct {
Field string `json:"the_field"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", fmt.Errorf("error parsing API response: %w", err)
}
return response.Field, nil
}
IMO this is a good level of abstraction for a language's standard library. Each of the concrete steps required for the process are simply and adequately represented. Each can fail, and that failure is idiomatically managed directly and inline, which, when applied to an entire program, significantly improves reliability. If you find yourself doing this often, you can easily write a function to do the grunt work. Probably func getJSON(url string, response interface{}) error
> Same deal with a worker pool. Concurrency is great, but instead of providing a robust, well written solution as part of the language itself, it gives you a toy like thisGo's concurrency primitives are very low level. This can be bad, but it can also be good: not all worker pools, for example, need the same set of features.
I always thought code written as page after page of low-level details was bad code. I thought the same thing about code written as class after class after class of OO hierarchy. But people talk about Java and Go as if it's impossible to write unreadable code in them. I don't think code has to contain a single hard-to-understand statement to be "unreadable." After all, code that is unreadable because of abuse of powerful language constructs isn't literally "unreadable." You call it that because reading it requires an unreasonable amount of time and effort. The same thing can (and should) be said about code that requires an unreasonable amount of effort for any reason.
To me, it's just different ways that programmers can waste your time. One programmer might waste your time by combining powerful language features in cryptic ways; another might waste your time by hiding crucial structure in a vast sea of details. What's the difference?
I've found "go doc" amazing for this use-case; I only ever trawl through the source-code for a high-level understanding as a last resort - usually because the code is undocumented or under-documented.
I completely agree with this. The best code is code you don't have to read because the structure of the code makes navigation easy and functional boundaries obvious. A language that doesn't provide strong support for declaring functional boundaries results in code that is much harder to read because you have to comprehend a lot more of it to know what's going on.
Few months ago I started working on a Go codebase. Yes, while the language is simple, you can absolutely make code confusing. If you wrote code yourself it is obviously simple to you, because you know the structure. But it can be a nightmare to someone else who needs to learn the structure and only has the code.
I understand the kind of joy-of-expression that lives in things like https://poignant.guide/dwemthy/, but if you attempt to put that in a production codebase, I’m not gonna be at-all positive in code review.
Save art for the canvas, for your weekends, for your loved ones. Bring a professional self to your job.
The way people fail in this line of work, if they have skill, is burnout. Burnout is the thing that'll get you. So you do whatever you can to stave it off - and that requires working on something you actually care about in some way.
This is the hallmark of a professional (very likely mature and senior) team member. Nothing to prove and interested in a maintainable project.
There are times for clever, no doubt. Every rule has an exception.
Bill (William) Kennedy at Ardan Labs has a line he uses in his talks: the bottom level developers need to grow and come up and the top level developers need to avoid cleverness and come down and everyone meet in the middle.
If you think this is different than what the parent is describing then you’re doing it wrong
Some languages have a big bag of tricks, other languages let you extend them to the moon, and still others make you work at it a little. In the end it's all just computation, and the tool choice can be reduced to a list of "must haves" and "cannots". If you need more expressive power -- make your build a little more complex and start generating code, give it a small notion of types or static invariants. It only has to generalize as much as your problem does, and that leads you to build the right abstraction instead of dumping an untried language feature on the problem in the hope that it is a solution.
There was a thread on the Rust reddit where someone was asking how to do something relatively simple using some elaborate combination of map/reduce/filter/continuations/who-knows-what, and someone said "just use a for loop", and the OP was enlightened.
People don't know how great the burden of trying to model their problem to fit a fancy language is until it's gone. I didn't.
I want generics and sum types, but I miss them less than I would have predicted.
I think this is a process of learning. While learning a new tool you start to model problems so you can practice, even though not necessary. Once comfortable you realize when to use the tool and when not.
(Also, rust’s for loops are implemented in terms of iterators; they’re actually the more primitive construction in a language sense; a while loop with the Iterator library API.)
I also write Go code without missing generics, but that’s because I’m also fluent in other languages, and tend to use those when I want something ill-suited to Go, rather than trying to force Go into that shape.
I think this should be the main takeaway from people learning go - it's not suited for everything. Technically you can write "World of Warcraft" in pure assembly, but it doesn't make sense to do - you're using the wrong tool for the job. My problem is I hear a lot of people advocating for golang with a one-size-fits-all, theres-nothing-better, sort of mantra.
I have things I absolutely reach to golang for, but the sweet spot I've found is to re-implement a prototype I've built in some other language (like Python) when I need the speed. Trying to actually create new things in golang is tedious and I end up fighting the tooling more than most other languages (sans maybe C++ or Java).
Pun intended? If so, nicely done.
I think it's hard to discern between "that overly-complex functional and declarative definition is unfamiliar to me" and "that is way over-complicated and should just use a for loop".
Any chance you can track down that example so others can compare the two examples as well?
""" The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. """
I read that as Rob Pike saying he wrote go for idiots google hires to write.
https://bravenewgeek.com/go-is-unapologetically-flawed-heres...
http://joeduffyblog.com/2016/02/07/the-error-model/#forgetti...
> It’s surprising to me that Go made unused imports an error, and yet missed this far more critical one. So close!
Result/Option/Maybe types force unwrapping, which makes ignored return codes auditable and allows you to manage technical debt.
This doesn't speak to Go's simplicity so much as it does to Go's conservatism. Having the Go standard library use sum types, establishing a precedent and a culture, would be no more complex in the absolute, but would have been more of a stretch for its initial target user base.
The most egregious to me has always been that unused imports are an error but variable shadowing is not.
Even in languages with dynamic side-effecting imports (like Ruby or Python) I've never seen a bug caused by an unused import. Not so for shadowing (don't get me wrong, it's a convenient feature, but if you're going to remove this sort of things because reasons it's a much bigger pitfall than unused imports).
import "foo"
func other() {
shadow var foo string = "bar";
}[0] and go is actually weirder than that — and the opposite way 'round — as `var` doesn't allow any shadowing in the same scope:
var a, b int
var a, c int // fails because it redeclares a in the same block
while `:=` allows same-scope shadowing as long as the overlap is not complete: a, b := foo()
a, b := foo() // fails because no new variable on the left side
a, c := foo() // succeeds
both allow arbitrary shadowing in sub-scopes. a := 2
a = 3
By definition you must have nested scopes to have shadowing. Within the same scope, it's only ever assignment. let a = 2;
let a = 3;
I think you would say the latter shadows the former...There is already a “let” to show you that a variable is being created, adding more verbosity to a feature that, in some sense, is about removing verbosity kinda misses the point, in my opinion.
That said, never say never, but if I was a betting kind of person, I’d bet against it ever being accepted.
For example I often write code like this in Java:
String ageString = request.getParameter("age");
int ageInt = parseInt(ageString);
because I can't re-use name `age` twice and forced to distinguish between those names.Now I agree with you about imports. I often want to comment a line and run program. Now I have to comment a line, run a program, encounter compilation error, find import, comment that import, run again. And uncomment two lines later. With Java my IDE optimizes import and removes all unused imports when I'm commiting my code. While I'm working on my code, I'm absolutely fine with any warnings. I would say even more: back then when I used Eclipse, it had awesome ability to compile even code with errors. This code just throws exception on runtime. But if I'm not really interested in that snnippet and working on other part, I can run it just fine. Probably that feature is the only thing that I'm missing from Idea.
Shadowing exists in most languages, the biggest difference being the allowed scope relationships between the shadower and the shadowee: most languages allow inter-function and inter-block shadowing (if they have actual block-level scope so e.g. not Python).
Intra-block shadowing is a much rarer feature, and one which Go doesn't have.
I use it in most of my open source projects.
You could give this dialect a name and then maybe the compiler could enforce rules on projects that declare that they’re using that dialect (like C compilers do with dialects like “c19” vs “gnu99”), but it’s not strictly necessary; you can also just create “dialect tooling” that wraps the language’s tooling and adheres to those rules (like Elixir’s compiler wraps Erlang’s compiler.)
And a CI shell-script, or git pre-commit hook, that runs a linter before/after running the compiler, is an example of just such “wrapped tooling.”
I think this really hits the nail on the head. There are benefits to a conservative approach, but it's not the same as simplicity.
I would challenge this. I would say _some_ of it is wasted, but most of it works towards making the code more understandable. And code being understandable to the next person to read it (both in the small "what is this block doing" sense and in the larger "what is this algorithm doing" sense) is very important.
Even Go did this by adding async features into the core language. This is a complication that doesn't exist in the older languages it was intended to build on, but by building that complexity into the language, they reduced the burden of using it in your code.
Go's simplicity and heavy idiomatic culture means all the code more or less looks identical. This is great for team projects.
It is highly stimulating intellectual effort though. Sometimes I sit down and spend hours just thinking about the best way to do something. It's some kind of philosophy: the abstractions we create reflect the way we understand things. To write good code, we must study the computer science and the problem domain itself.
Without this, it's just boring mechanical work. Once the project has been figured out it ceases to be interesting. Some of my projects are unfinished because I can't justify spending more time on them even though I know exactly what must be done.
¹ Yes, even over correctness.
How easily can people understand what this code does and how?
This is at least conceptually objectively measurable, though I don't know of any actual attempts to do so.
That's why things like C coding style is so variable - sometimes within the same body of code (see net-snmp ....)
I'd rather have consistent coding standards but I daily deal with different team projects with different conventions, so I'm used to adapting my own reading conventions as I switch contexts.
Keep a common aesthetic within a project. It's worth it - by measure of success of a project.
The argument that "all code gets sloppy so let's just have very verbose code from the get-go" is pretty insane to me.
Correctness is a basic starting point. It's the minimum viable offering.
Here is my counter argument.
Readable code that has some bugs is fixable. Because you can understand what it's doing and how to change it.
Working code that is unreadable is basically dead. No one can make changes to code they don't comprehend. The only thing it's good for is running it as is, much like a compiled binary.
I rather disagree that making your code readable and maintainable is a waste of effort.
Saying your code is going to "look like sh£t anyway" seems rather defeatist, and an _excuse_ to write unreadable code.
You don't see people in Go doing this, and it gives the impression that the community is small, but I think they're just building stuff.
Sometimes, these connections can be unclear from the outside. For example, there’s a lot of talk about “generic type constructors” and “generic associated types”, which sounds academic. However, it’s something the compiler needs to understand in order to implement a very simple user-facing feature: async functions in traits. From the outside it may look like “oh those folks are out of touch” but it’s directly connected to real user needs.
(As a further aside, these two features are identical, but “generic type constructors” focuses on the academic, and “generic associated types” focuses on the end-user benefit; we changed out terminology here specifically to focus on what users get out of the feature rather than the type theory implications.)
Furthermore, some people tweeting about things they’re excited about does also not preclude others who are heads down all the time. You wouldn’t see them for the same reasons, they’re not tweeting.
These kinds of swipes against other languages lower the discourse and promote animosity when there really should be none. I’d encourage you to consider if these kinds of attitudes help bring about more people who are interested in building cool things, or fan flame wars that distract folks from doing exactly that. Every minute spent arguing over whose language is better is also a distraction from building cool things as well.
We rejected a proposal for dependent/pi types; we’re still adding a limited form, and may get there someday, but we didn’t want to go fully into them at first because the difficulty was high, and the benefit less clear, than just the simple version. (Const generics)
There’s a few other other features that we had and removed too I can think of off the top of my head. We used to use conditions for error handling. We had typestate.
There was a battle over type classes vs ML style modules, type classes (traits) won in the end. That doesn’t mean modules are useless...
I think the answers to these questions are very relative to your langauge’s values and goals. All of these features have good uses in other languages, but couldn’t find a place in Rust for a variety of reasons. Your language should be different than Rust, so you may find some of these features useful, and not find some of ours useful.
I would encourage you to read TAPL, I think it would help with what you’re trying to do. Oh and check out https://plzoo.andrej.com/
Not always, some Rust community members in fact don't build any sort of applications with Rust at all, and only build libraries. I think it's reasonable to be skeptical of this.
I think the community can do better by promoting more talks involving applications written with Rust. As an outsider this is a puzzling omission to me as there seems to be more people using Rust than there is Firefox and Cloudfront engineers, so I'd like to learn more about where it's actually used.
This is obviously false. How do you square that with all the Rust code we've shipped in Firefox, for example? I build things in Rust every day.
>> This is obviously false. How do you square that with all the Rust code we've shipped in Firefox, for example? I build things in Rust every day. - pcwalton
> I didn't state that no one builds anything in Rust so I have no need to square it. - Touche
Touche!
You are technically correct I suppose. You didn't state no one builds anything, but arousing suspicion that "a lot" of prominent Rust people aren't building anything and are just "snacking" on the language is pretty pointed rhetoric with an obvious purpose.
You could start up a Programming Language tabloid with a headline like:
EXPOSED: PROMINENT RUST PROGRAMMERS CAN'T EVEN WRITE IN RUST!
And really that's all before analyzing the line of logic of "people tweeting only about interesting language features and not their personal projects, public work projects, or private work projects implies they might just not be building anything at all" which seems pretty flimsy at best.