I adore Go's concurrency model but loathe go's actual language. The constant repetition in error handling, lack of generics and lack of parameterised types and Option<> make it feel like a children's toy set version of C instead of a useful modern language akin to Rust and Swift (and modern javascript).
I really really love the concurrency model though. And as far as I can tell there isn't much like it available elsewhere. You can make a similar concurrency model in rust, but you have to use much heavier OS level threads to do it, and all the other crates don't support it. Erlang / elixir do it but come with a much higher runtime performance penalty. Pony is interesting but very new - there aren't a lot of libraries for it and when I was playing with it the compiler seemed crazy slow.
I'm a little bit tempted to make a simple compile-to-go language. I'd get lynched at Go meetups for fragmenting the ecosystem, but it might be worth it. I like almost everything about go except the language itself.
I would love this; I feel entirely the same. The runtime is pretty good, performance is nice, concurrency is great, the tooling is wonderful (if you ignore the GOPATH nonsense and thew lacklustre package management solution). The language itself is quite frustrating to use, full of needless repetitive boilerplate and a mediocre type system.
I'd love a similar language with all the goodies that I find make development safer and more productive: proper enums, pattern matching, sum types, generics, and a handful of other features.
This is one of the better articles that shows both when comparing Python, Go and Elixir.
https://medium.com/unbabel-dev/a-tale-of-three-kings-e0be17a...
For example, there is the May[1] concurrency library. It provides alternative implementations of the standard library's IO interface, but does Go's automatic suspend/resume, so it still looks like blocking code, which is nice. From what I can tell it's still early days on a one-person project, but it is interesting, at least.
On the other hand, the concurrency OOTB languages have all had much better ecosystems and experiences; C#, nodejs(now and w/TypeScript), F#, Golang, etc.
Inferior approaches sometimes do get baked in to standard libraries. Having a culture of versioning, deprecating, migrating, is better IMO. But it's even better again to do a really good job first time around. Not easy, but I didn't say it was!
libfringe is another extremely interesting player in this space.
I don't know about "might be worth it" part, and I don't think Go is success is because of it's concurrency model. Instead, I think Go wins because it can get thing done quickly, in one stop.
The concurrency model has been designed to help achieve that, and the huge battery pack came with the language is also for help achieve that.
I'm not discouraging you from design your language, though. In fact, I could be very happy to see a new language which can give me all the benefits that Go gives me and at same time just ... simply be a better language.
My ideal language (in my opinion) is a combination of Go and Rust: Big standard battery (Help me get things done and encourage to it's ecosystem)(&BTW, it don't have to be in the standard library), and fearless programming (Help me avoid mistakes).
Sadly Rust don't want to have a big battery for some reason :(
Rust is against pulling it in the standard library because that forces them to make additional stability guarantees. When you do that, you end up like python's `urllib`/`urllib2` situation.
That's what I did for my current project. I love Go but for web apps is not the best in my opinion. I had some spare time and ended up building a VM that compiles typescript to bytecode. The performance loss is minimal, I still use the Go std library but it is a pleasure to use VS Code, generics (the VM ignores types but the TS compiler gives you static type safety, autocompletion, refactoring, etc...) Also exceptions is great for web apps where usually there is nothing else you can do but save all the info that you can and show an error to the user.
I hardly ever use classes unless I'm extending a vendor library. I have never used generics. Why are generics such a critical component of a programming language that every thread about Go mentions it? It's an honest question from me.
Go has built-in generic arrays, slices, and dictionaries, but nothing else, and you can't define your own functions that work on those without specifying the type. So say you write a function that shuffles the elements of an array, like Python's random.shuffle(list). You can't make your function work for all kinds of arrays. You have to have a different function to shuffle arrays of strings, arrays of ints, arrays of FooBarClass, etc. even though the shuffling logic doesn't give a damn about what sort of thing is in the array.
I have a suspicion that this occasionally leads developers to write code inline that they previously would've extracted into a utility function. See the answers here:
https://stackoverflow.com/questions/12264789/shuffle-array-i...
So essentially, containers and function overloading is damned near impossible. Got it.
def example(a):
return a
Would be perfectly legal python code. But in Go you would have to choose the type of A and duplicate the function under a different name if you want it to work with a different type.So, when a language implements static typing without generics they actually mean 'lots of code and approaches legal in Python would be rejected'.
So it doesn't add a feature to the expressiveness of the code. It fixes a bug in the type system so that the expressive code is considered legal. Of course better type systems have existed since the 70's, but the set of people that know the ins and outs of how to implement those and the trade offs behind it does not include Rob Pike. His interests and skills are different (and the cause of some of the better features of Go)
Personally, I do not feel that strongly about generics; it would be nice to have them, but for my purposes, I can live without them.
But still: generic container types would be very nice. I don't need to use it very often, but Go's sort.Sort is very uncomfortable to use. If Go had proper generics, it would be easier to define a "generic" interface to iterate over things - currently, Go's builtin slice and map types are privileged over user defined types. There are probably more issues I cannot think of right now.
None of those are deal breakers for me. Go is highly compatible with the way my mind works, to such a degree I can easily forgive it all the things I do not like about it. But I also work in C# from time to time, and seeing how the .Net framework uses generics makes me wish Go had them, too.
However, comparing to Python won't make much sense; people see generics as essential to statically typed langauges. You don't need them for dynamically typed ones!
Agreed. Something that's been underlined for me since picking up TypeScript in addition to JavaScript. I can take or leave TS when writing a script, but I consider it indispensable when writing a library.
That's the reason why the Go community feels generics are not the top priority (euphemism.)
Only in very specific cases (implementing data structures, for example) you can feel the need for generics. Maybe also in serialization, although that's really normal to pass a generic container (object, void *, interface{}) and use introspection.
I don't think it's such a bad idea, considering how stable the language itself is, it's a pretty solid bet.
Why not fix Go instead?
The lack of libraries is what drove me away, too. The type system is gorgeous, though. I really hope Pony grows a decent library ecosystem soon, the language itself was very pleasant to use once I got past the initial learning curve.
If you rephrased that as 'a templating language for generating Go code', it would slot into the 'go generate' build stage and people would love you (if you got it right ;) ). The only current solution to boilerplate and lack of generics is code generation, but the templating languages all seem to suck for Go code generation so people are stuck with writing Go programs to generate Go code (like the various enum generators), or attempt to marry Go code with the standard text templating which is awful to work with (like, say, Xo).
(Edit: BTW I do not necessarily agree that)
For example, consider trying to implement a List data structure in Go. What type does the List hold? It poses no constraints on the type of things put in it, except that they must all share that type.
The List can store `interface{}`, but then there's nothing to stop you from adding two different types to the list. And what type of value would a method like `getFirstItem()` return? Just an `interface{}`, forcing the user to cast.
Go's interfaces express constraints on individual types (e.g. type A has methods Foo and Bar) but not across functions defined on a struct (e.g. the type that a List stores is the SAME type that the getHead() function returns).
There are good aspects of Go concurrency like how the whole ecosystem is async by default (like Node's), but truly praising the model is something I mainly hear from beginners.
In terms of correctness, I've never heard claims about improving security issues, except in so far as those caused by memory/concurrency - think of it as necessary but not sufficient for security. This "limited" class of bugs is responsible for quite a large number of runtime issues & they can be frustratingly difficult to find/fix (+ be confident that you did actually fix it). It's hard to say how much better things will play out in real-world software development as at some fundamental point there are always unsafe calls which weakens the guarantees Rust can make (but does put an explicit boundary on where you should go looking for bugs).
As for compiler errors not helping you understand the problem, I have yet to encounter a compiler that does that. What Rust does do extremely well is that the errors are very clearly explained in the terminal (with an error code that has pretty good online documentation), but also gives you hints on simple ways to potentially alter the code to fix it. As a beginner, I've found it way quicker to fix those bugs (even within macros) than the compiler issues I encountered learning C++ - granted back then compilers were a lot worse on that front, but even these days with C++ I've struggled fighting the compiler/preprocessor.
Also, the Rust compiler appears pretty vibrant with lots of improvements being made to help with user friendliness, so it's possible that further improvements in inference might reduce the problem spots (it's already pretty magic to me).
I've started learning Rust a few weeks ago & those are my impressions so far.
I'm only comparing Rust and Go where they overlap. For example, Rust webservers versus Go webservers. There are other languages that may overlap different parts of Rust, such as in fact, Ruby and Python.
Go and Rust are both more general than the languages people compare them to. Both have C interop of various levels. Both allow unsafe code that touches memory directly. Both are high performance, relatively low level, and both provide some level of memory safety (though, Go provides much less.) I think they overlap a whole lot more than people think.
>In terms of correctness, I've never heard claims about improving security issues, except in so far as those caused by memory/concurrency - think of it as necessary but not sufficient for security. This "limited" class of bugs is responsible for quite a large number of runtime issues & they can be frustratingly difficult to find/fix (+ be confident that you did actually fix it). It's hard to say how much better things will play out in real-world software development as at some fundamental point there are always unsafe calls which weakens the guarantees Rust can make (but does put an explicit boundary on where you should go looking for bugs).
Well, I personally would claim it helps a lot of security and crash issues. Buffer overflows, use-after-frees, race conditions, and more. Also, most people do not overstate the safety of Rust, but beginners frequently misunderstand it. This is because people often list the benefits without listing the caveats.
Not very mainstream, but take a loot at elm's compiler errors [1]. They worked hard in this direction, and the result is both helpful and beautiful.
You can try loading up any of the examples in the online editor [2] and introducing a random bug, just to see how the compiler <del>barks</del> tries to gently teach you.
Additionally, there's https://doc.rust-lang.org/error-index.html
Huh? Those might not be sufficient, but are the source of 99% of security issues.
Both are highly atypical, once in a decade, CPU issues, so represent 0% of software related bugs.
I would argue that the rest of your paragraph talks about how Rust (indirectly) improves security issues.
> As for compiler errors not helping you understand the problem, I have yet to encounter a compiler that does that.
Try misplacing a { in an average LaTeX document. But don't say that you haven't been warned. ;)
Alternatively, write some C++ code that uses std::map<std::string, std::string> or something like that incorrectly, and marvel at the page-long exceptions with all the default template arguments expanded into an unreadable mess.
> Also, the Rust compiler appears pretty vibrant with lots of improvements being made to help with user friendliness, so it's possible that further improvements in inference might reduce the problem spots (it's already pretty magic to me).
I also recently got into Rust (coming from Go), and the thing I miss most is `gofmt`. Is there a standard tool-enforced coding style for Rust that the community agrees on, in the same way that the Go community has by and large agreed on gofmt?
I think perhaps I didn't communicate my meaning clearly enough. Rust does significantly reduce the risk of a certain class of security problems (reduce not eliminate since it's highly unlikely you'll have 0 unsafe{} blocks anywhere in your dependency chain). That's not disputable since that's part of the language design. That's certainly an advantage it has over C/C++. However, security is far more than just memory safety & I have read nowhere that writing more secure code is a design goal for Rust (I'm not even sure yet such a thing is possible).
> Try misplacing a { in an average LaTeX document. But don't say that you haven't been warned. ;)
> Alternatively, write some C++ code that uses std::map<std::string, std::string> or something like that incorrectly, and marvel at the page-long exceptions with all the default template arguments expanded into an unreadable mess.
I agree 100%. I think perhaps you misread what I wrote? I said I have not encountered a compiler where the errors helps you understand a language.
> Is there a standard tool-enforced coding style for Rust that the community agrees on, in the same way that the Go community has by and large agreed on gofmt?
rustfmt
While it's also not perfect or can correctly identify all possible syntax/intent errors, it's a valuable tool to learn the language nonetheless.
https://github.com/rust-lang-nursery/rustfmt
And is planned to be distributed with the compiler and the cargo stack by default
Only thing worse are Java generics, if only because they literally tried to tack them in 10 (heck, getting close to 15 years now) years ago and we are still feeling the consequences of those design choices today, (unless Java 8+ made major fixes to this. Haven't used it in enough detail to make a judgement).
Of course, but at the cost of requiring a garbage collector. Mandatory GC has three main drawbacks:
* It makes it harder and much less convenient to call libraries in this language from other languages.
* Low-resource embedded systems are a no-go.
* GC is typically a tradeoff between speed and memory usage. Manual memory management can be very fast and very lean.
Only if talking about microcontrolers with few hundred KB, where even C is a challenge.
There are Java and Oberon implementations for single digit MB, like Cortex-M4.
So? Just because you can use a language on a given system doesn't mean that you should use it in any serious context.
A GC typically makes memory usage and execution latency non-deterministic, or at the least very hard to analyze. If your washing machine software OOMs whenever the GC didn't run between two button pushes, you'll have a great Heisenbug.
Here is just one of them.
http://www.militaryaerospace.com/articles/2006/10/lockheed-m...
"PERC Ultra offered Lockheed Martin the responsiveness it needed to meet its most demanding timing requirements. In addition to real-time threading and deterministic garbage collection, PERC Ultra provided the instrumentation and VM management tools necessary to support the mission-critical real-time requirements of the Aegis Weapon System."
"The Lockheed Martin-developed Aegis Weapon System is the sea-based element of the U.S. Ballistic Missile Defense System. The Aegis Weapon System is a radar and missile system integrated with its own command and control system, capable of simultaneous operation defending against advanced air, surface, and subsurface threats."
Is that serious enough for you?
You could say same about malloc and free. Not deterministic at all either. Memory fragmentation is a huge issue as well, and can bring down the whole system.
Typical GC (not all flavors) has a huge advantage on microcontrollers: you gain ability to compact heap. No more fragmentation.
That said, mostly I try not to dynamically allocate anything in firmware or kernel drivers. Whenever possible. Sometimes I write my own specialized allocators. For example a simple wait free allocator fast and rugged enough to call from an interrupt service routine.
C is the default and not a challenge on 8-bitters.
That's close enough in my book. Those MCUs are tricky targets and I can certainly live for example with not being able to pass structs as return values. Or without re-entrancy. Perfectly understandable once you take into account limited IRAM space, 128 or 256 bytes, where stack, register banks and most of your temporaries and globals need to reside.
Just in case you're actually trying to engage in good faith, though, I'll try this one more time. If I have a compiler that is less-than-100% standards compliant, I may not be able to use a few features of the standard. That means there may be a few ANSI C idioms that I can't use. Of those, the number that I would choose to use on that size of processor is very, very few. So in practice, there is no "challenge".
Why would I use very few of these features? Because on a processor that size, you're not writing a huge app. You don't use all the functions in the standard library, you don't use all the keywords, you usually don't push the language very far at all. At worst, you might have to develop one or two idioms of your own. It's... mildly annoying, rather than the big deal you're trying to make it.
| impl | c | py3 | mpy | pypy3 | cy3 |
|------|-------|--------|---------|-------|-------|
| [ns] | 0.176 | 11.462 | 37.633 | 0.818 | 0.590 |
| .c | 1.000 | 65.126 | 213.824 | 4.648 | 3.352 |
mpy - MicroPython
cy3 - Cython
So, ... low resource can't run heavy languages fast (yet?). Good thing is by using MicroPython and writing the time critical stuff in C you can get the best of both worlds while paying the least.
edit: sigh, markdown table
| impl | c | py3 | mpy | pypy3 | cy3 |
|------|-------|--------|---------|-------|-------|
| [ns] | 0.176 | 11.462 | 37.633 | 0.818 | 0.590 |
| .c | 1.000 | 65.126 | 213.824 | 4.648 | 3.352 |
Also, I'm taking a guess here and assuming that the values in the second line mean "execution time as multiples of the execution time for C".When you interoperate with another GCed language it's even harder, virtually impossible, because both languages' GCs assume they own everything and so anything that's visible in both languages will be freed twice.
Using GC languages doesn't mean doing 100% memory allocation via GC.
Others on that list have specific language features for GC free allocation.
There's lots of detail in the D language garbage collection docs:
https://dlang.org/spec/garbage.html
The whole thing might be instructive, but 28.3.3 has a bunch of particular expectations of the D GC.
The comments of jonathanstrange and humanrebar are also spot-on.
That being said, Rust is already so obscure that it can easily replace C++ and I predict it a great future. Programmers love obscure programming languages with steep, long learning curves that allow them to show off.
...except the outer, most superficial level. I'm genuinely afraid that it will never "catch on" because it just looks weird. (But not weird enough to attract that kind of people.)
It's a shame because
- Ada generic packages are exactly what C++ templates should have been
- Derived types and record extension is inheritance that makes sense
- Access types are obvious in hindsight
- Correct terminology for procedures and functions does help a lot in communication
- RAII in the shape of controlled types feels a bit bolted on but it works very well
- Named blocks and explicit closing is easy but very useful
- Class-wide types are deemphasized the way polymorphism should be
- The -gnatyy flag is almost as good as gofmt.
- Tasks and protected types form a very intuitive and safe concurrency mechanism
- Having contract-based programming as an option built into the language is way superior to relying on asserts in the procedure
- While not always budgeted for, when there is time to spend, SPARK is uh-mazing. Strong guarantees at relatively low cost, and reasonably easy to learn as well.
Is it not? It doesnt contain recipes, no, but it fully documents the fundanental units of the language youll express your solution in.
Yes, Stepanov's first attempt at writing something like the STL was done in Ada. But no, it wasn't "the STL" - it was neither Standard nor Template.
And Stepanov abandoned Ada for C++, because Ada wasn't expressive enough for what Stepanov was trying to write.
And yes, it wasn't quite the STL, we had quite a few variations of it, the most well known coming from SGI, until things kind of settled at ANSI.
And C++ was among the better candidates for the language to use. It was more suitable than Ada.
And, do you have any basis for the statement that Stroustrup advocated C++ to Stepanov?
"And, of course, Andy and Bjarne Stroustrup are responsible for putting STL into the standard."
"The support of Bjarne Stroustrup was crucial. Bjarne really wanted STL in the standard and if Bjarne wants something, he gets it. He is as stubborn as a mule. He even forced me to make changes in STL that I would never make for anybody else - I am also stubborn, but he is the most single minded person I know. He gets things done. It took him a while to understand what STL was all about, but when he did, he was prepared to push it through. He also contributed to STL by standing up for the view that more than one way of programming was valid - against no end of flak and hype for more than a decade, and pursuing a combination of flexibility, efficiency, overloading, and type-safety in templates that made STL possible. I would like to state quite clearly that Bjarne is the preeminent language designer of my generation."
But you said,
> Bjarne advocating him to use C++ instead [of Ada]
which your quote here doesn't substantiate at all.
"My attempts to implement algorithms that work on any sequential structure (both lists and arrays) failed because of the state of Ada compilers at the time."
"In 1987 at Bell Labs Andy Koenig taught me the semantics of C. The abstract machine behind C was a revelation. I also read lots of UNIX and Plan 9 code: Ken Thompson’s and Rob Pike’s programming style certainly influenced STL. In any case, in 1987 C++ was not ready for STL and I had to move on. "
"In 1993, after 5 years working on unrelated projects, I returned to generic programming. Andy Koenig suggested that I write a proposal for including my library into the C++ standard, Bjarne Stroustrup enthusiastically endorsed the proposal and in less than a year STL was accepted into the standard. STL is the result of 20 years of thinking but of less than 2 years of funding."
Fair enough. I forgot he was encouraged to go to C++ by anybody.
Then you might be interested in https://nim-lang.org/ as a Pascal-like Rust alternative.
An obtuse syntax is a big downside of C++. Implying that a steep learning curve and the ability to 'show off' helps keep the language where it is grossly misrepresents the vast majority of C++ programmers. Those traits were born out of necessity. In C++98 you simply had to be 'clever' to keep up with modern languages because the language was stagnant for a decade. If the committee can't add things, you do something clever and do it yourself.
C++ was stagnant for the dot-com era, Web 2.0, and the rise of mobile apps, and today it thrives. There's more to that than being entrenched in legacy code bases.
Rust emulating C++s difficulty as a way to cultivate an elitist community, if that's what you're implying, will not work now when C++ is moving in the opposite direction.
Ada 83 has
- arrays,
- records (structs),
- derived types (subtypes carrying the same data as parent type),
- subtypes,
- access types (thick pointers),
- procedures (subprograms executed for their side effects) and functions (subprograms that return values),
- Named parameters
- reference arguments (greatly reducing the amount of pointers you have to deal with)
- Default values for arguments
- overloading by type
- in/out parameters
- private package parts (encapsulation)
- generic packages (kinda like C++ templates)
- exceptions
- tasks (concurrent processes that communicate with message passing)
-----
Ada 95 adds a lot of things that make it easier to do Java-style OOP:
- Record extension (inheritance; subtypes carrying additional data their parent type does not)
- Dynamic dispatch of subtypes
- Abstract types
- Subprogram access types (function pointers)
- Sophisticated package hierarchy (though potentially somewhat unintuitive: child packages extend their parents, parent packages are not umbrellas for children the way they are in Python)
- Protected types (protected objects)
- Modular types (unsigned ints with defined overflow characteristics)
- Unbounded_String (the std::string of Ada, compared to the char[] situation in Ada 83)
-----
In Ada 2005, the greatest addition is probably the Collections packages, which added several common data structures to the standard library:
- Instance.Method(Param) syntax sugar for Object.Method(Instance, Param)
- Interfaces
- Controlled types (RAII)
- Removed silly restrictions on access types
- Keyword "limited" to import only declarations
- Keyword "raise" takes exception message directly
- Pragma Assert
...and this is where I need to get off the train. Anyway, bookmark the Ada 2012 Reference Manual. It has everything in it.
Edit: ...but look what I found! https://www.adacore.com/about-ada/comparison-chart
It's focused on safety even more than Rust and yet it has kept an understandable syntax.
Do you have an example of a language that does what you're looking for?
On the proof side, Rust's built in unit testing is great, and allows for quick validation of code (proofs). But I think you mean something different.
> Go works wonderfully
Go does work wonderfully, you should definitely use what you like. For me personally, though, Go never excited me. Rust on the other hand continues to be exciting, I always feel like I'm learning something new (and I've been using it for the past 3 years).
> For example, you could always just clone memory at every occasion, return the input instead of borrowing, etc. In fact, these things might be easier for a beginner to do.
I think most Rust beginners (I know it was true of me anyway) do often just clone everywhere. Eventually, you then replace that with Rc or Arc... And then one day you decide you want the fastest thing on the block, and now you know the in's and out's of Rust, so you decide to up the ante and put lifetimes on everything.
I recommend this pattern to all Rust beginners.
Dependently typed programming languages such as Coq and Idris let you write proofs about your programs. These languages are fairly academic, and most engineers probably won't find it worthwhile to use these tools. Personally, I love them. I wrote a short article about my experience with Coq: https://www.stephanboyer.com/post/134/my-unusual-hobby
> Do you have an example of a language that does what you're looking for?
You mean, like SPARK?
> On the proof side, Rust's built in unit testing is great, and allows for quick validation of code (proofs).
No, unit testing is not proof of correctness, it's a hint that your code has no regressions at best. I'd take a proof over unit tests any time of day.
Note that unit tests prove only that the code works for those inputs and those code paths tested. Mathematical proofs are supposed to be exhaustive.
I haven't had the time to learn it yet, though I would like to.
At least equally importantly, we need a new generation of developers to grow up with these tools before they can become the norm.
In fact, the biggest contribution that academia could make to safe programs is to replace Java and C by Rust in all the programming courses, so that the next generation of developers is raised on Rust and makes that language (and its strictness mindset) popular in the industry in the same way that Java's relevance in the industry is based (in part) on its prevalence in curriculums.
Rust isn't the first language to emphasise correctness. We've had Ada for decades, but it's not taken over the world.
Going to the extreme, full-bore formal methods will never be taught as introductory material on programming courses for the masses, but they will continue to be taught at good universities.
I'm not sure this is a bad thing. For most applications, ad-hoc develop-and-test makes good sense. RAD is important in some domains. Both formal methods and highly strict languages have their downsides.
There's more to correctness than language, of course. A shift toward correctness could be as simple as encouraging students to put runtime asserts in their Java code.
Unit testing can only proof one instance of the input domain, e.g. the function square() returns 4 under the input 2. Languages like Coq allow you to proof that the function square returns the squared input for every possible input.
I see way more future in one's ability to write proofs of correctness in the comments before the function definition in any language; rather than preferring any specific language for the sake of proof.
Well, Rust does push for strong algebraic types. They aren't as expressive as other languages (the lack of higher order types irks me all the time - no, hygienic macros are not a good alternative) but they are the best ones you will find on any bare-metal language.
So I think that Rust will advance the overall state of the art, just a bit.
Rust is certainly a step in the right direction. I hope the toolchain and so forth end up in a state where all forms of users can love it, though; I have seem some reasonable opposition to including Rust in kernel code, and that sucks.
* That Rust is only harder because it enforces 'correctness.' It certainly is harder because it enforces correctness, but it's also harder because of how. I'm not saying there's a better approach to this, but I think a lot of people are implying that there isn't, and I don't think that's a safe assumption. I think that we could find ways to make equally memory-safe languages that go about enforcing safety in entirely different manners than with ownership and lifetime semantics.
* In fact, the entire idea that Rust enforces correctness. Only if your definition of 'correctness' to be memory-safety, but I would normally define 'correctness' to include rigorous mathematical proofs. Rust's safety guarantees are often accidentally blown out of proportion; they mainly aid in preventing security and concurrency bugs, but only a certain class of each. This is still useful, but this caveat really needs to be in your face more often, as a lot of people will not mention it when touting the benefits of Rust, and beginners can get easily confused about what exactly Rust prevents you from doing.
* The idea that Rust's approach is always worth the trade-offs. Go is another programming language I like, and there are definitely things that are simply easier to write in Go with few disadvantages. Fearless concurrency is a wonderful feature, but for embarrassingly parallel problems like, often, web servers, where each thread is usually independent in terms of mutable state, Go works wonderfully. It also lets you shoot yourself in the foot in a way that Rust wouldn't, but often for a lot of simpler apps it still ends up being easier.
* The idea that solving the compiler errors makes you understand the problems correctly. For example, you could always just clone memory at every occasion, return the input instead of borrowing, etc. In fact, these things might be easier for a beginner to do. There will probably be a ton of Rust anti-patterns that come about from trying to resolve compiler errors.