The Vale Programming Language
vale.dev
vale.dev
0 - https://github.com/microsoft/verona/blob/master/docs/explore...
Is my intuition correct that one could implement this type of memory handling in Rust as well?
It’d be super if regions were a mathematically robust way of describing that engineering solution.
The rest of these applications have either hard realtime or hard performance requirements or both. What I don't see in these apps, is for memory safety issues to be 'ok'.* It's 2020, using a memory safe language should be table stakes for new projects at this point.
----------
* The one exception I can think of is single player video games where the worst that can happen is basically just a crash to desktop. Multiplayer games are a different story especially with the trend toward paid DLC where these kinds of issues could result in anything from exploits in multiplayer matches (hurting the community and thus the game overall) or players circumventing DLC restrictions (resulting in a loss of profit).
I don't know if explicit regions help very much with safety, but you can certainly go places with a more expressive type language. E.G. ats - http://www.ats-lang.org/
Note that my knowledge of regions comes mainly from Cyclone, not Vale or Verona, are those different concepts with the same name?
Rust is pretty nice, but the options for allocation and it's ownership type system make it a mismatch for certain types of applications (probably games and OS's but we'll see how that plays out).
I think regions are neat. And I'm looking for an opportunity both for playing around with it myself and for the programming language community at large getting more exposure to them.
More ideas exposed to more people leads to better ideas of how to fit everything together and how to leverage what features to solve some problems better.
We'll see as things continue to develop here.
I think Rust for core engine and Lua for game logic would be a pretty solid combo.
What Vale does that is attractive is to make simplifying assumptions that address this problem domain closer to the level of granularity that it deserves.
I don't think that's doable unless the underlying language already has the same ownership and lifetime guarantees as Rust does, otherwise these bindings have to protect against memory issues by effectively taking ownership of the data to garantee its lifetime, which is not zero cost.
I simply don't think that you can auto-generate zero-cost safe C or C++ bindings automatically.
As stated, that's likely an accurate statement, but if you separate the requirements for the bindings the problem looks way more tractable:
- auto-generate: I'm pretty positive that this point is only hard given the following, and I feel you would agree with that assessment, so I'll just focus on the other ones.
- zero-cost: doing zero-cost ffi is the best option, but a lot of advanced constructs that people want to use cannot be well represented in that way because they rely on the internal consistency of the host language. That means that dynamically loading a library written in Rust might make it so that you can only interact with ADTs, trait objects and whitelisted provided traits on those ADTs, potentially only through trait objects. That's not zero-cost, but it is super powerful. ObjectiveC has lived its entire existence going through vtables and that has even enabled runtime introspection that was super useful.
- safe: safety can be accomplished easily enough if you don't constrain yourself to zero-cost or if you put constraints on what is supported for zero-cost ffi. You could say that method calls on trait objects is safe, but type parameters are out of scope because ffi and monomorphization are not compatible. You could restrict yourself to either no borrows crossing ffi, or no mutable data (only internal mutability through trait methods) allowed.
- C or C++: I would focus on other languages, personally. Having easy interop with Java, C#, Python and JavaScript would open opportunities that do not have anything to do with systems programming per-se, but that would make it more likely that the next numpy isn't written in pure C.
- automatically: the need for extra annotations in either end of the interop simplifies part of the problem and might even help with documentation. Adding some light ownership information to APIs in non-Rust languages would be great for non-Rust users of those APIs.
But I think it's inevitable you have to do things on both sides of the language boundary, i.e. pushing them towards a lowest common denominator. (e.g. Microsoft's COM, which actually worked pretty well)
Really what it pushes you toward is wire protocols and NOT relying on the type system. That is the C and C++ ABIs are related to but different than the APIs (type system).
My experience tells me that transparent interop is kind of a pipe dream. The problems always pop up somewhere. By "transparent", I meant "Rust function calls C++ function" and "C++ function calls Rust function" without other metadata/bindings. The codegen becomes a big problem in practice.
Fundamentally a lot of languages LOOK the same but they ACT completely differently. A Rust function is not a C++ function is not a Python function is not a Go function. (And funny thing -- as of C++ 11, C++ now has many different notions of "function", because of move semantics).
And functions are actually "easy" compared to types (e.g. inheritance vs. typeclasses vs. interfaces)
----
Basically I would say the problem is that you either spend time manually wrapping or annotating your code (as you say), OR you put an ever growing list of heuristics in the code generator, a la SWIG.
Those heuristics have bugs, and will make your program unreliable. They're also "someone else's problem", which leads app developers to come up with horrific workarounds.
So I go with the simple manual wrapping, and reducing the number of things to wrap by changing the structure of your program. This also has other benefits like efficiency, i.e. crossing the language boundary less often.
(Related: I think better build systems can go along way toward solving this problem. Unfortuately there seem to be a lot of language-specific build systems and package managers now, which only exacerbates the interop problem. If you have one language-neutral build system, it's not that bad.)
The good news is that whatever issue you have can probably be solved without resorting to FFI. There are plenty of other interop options - for example, if you control the library, you can use your operating system's IPC mechanisms with an agreed-upon serialization format. Or, if your library and application are within the same ecosystem, you can use language-specific library management features such as Python's import or Rust's crates. FFI is a bit like C itself - if you're reaching for it as a solution to your problem, then your finger is already on the trigger of the footgun.
If you have code in Ruby and in Julia that you want to mix together it's probably because both pieces of code leverage the distinct capabilities of those two languages, which have nice capabilities because they accepted very tradeoffs.
Would you really want Julia with Ruby's Number class? It would entirely kill performance. You want it to be an integer maybe, or a double, or sometimes something else. It's going to be application specific is the point, because another user want some different subset of Number's behavior. I don't think there's a shortcut here besides making a very limited or opinionated FFI system.
So from this perspective, the interop target should actually not be the lowest common denominator, but the opposite: a target that can describe all possible user languages in a common format.
In fact, I would actually argue that the implementation details are NOT that interesting and only a distraction: that we need an abstract way of declaring compatibility. For instance, one can imagine a high-level FFI target where function types are parameterized by their calling convention i.e.:
`foo: (VOID<C>->VOID<C>)<C>`
`bar: (UNIT<Rust> -> UNIT<RUST>)<RUST>`
and attempting to call `bar` from `foo` involves a compiler intrinsic like `RUST_CALL(bar)()` to convert the type (and the calling convention) appropriately.
https://dconf.org/2020/online/index.html#schedule
https://dlang.org/blog/2020/11/20/dconf-online-2020-how-to-p...
It's free to participate!
Even in C-like languages, generics were part of C++ (or rather templates, in that case) since the beginning, I think (that's 35 years ago). I think you can make a case for type inference reaching the C language family recently, though.
None of these features are technically novel but they do tend to be shared by newer system languages, as opposed to C/Pascal/Fortran/Cobol and friends. Then of course there's C++ that's both old-school and modern because C++ is slowly but surely evolving to become the programming language equivalent of the Borg and eventually all other programming languages are bound to become a subset of C++.
God help us all!!!!
(fortunately for my sanity, I have zero confidence that this will be the case)
aka the status quo of 60 years ago
Buzzwords are abound in modern technology circles anymore.
To your point; "modern" is especially insidious because it implies that older technology is bad. I'd take vanilla PHP and JavaScript over TypeScript and the frameworks any day. I recognize that's an extremely unpopular opinion but those languages are battle tested over 25 years and if I run into a problem that I haven't seen before, there's a high probability I'll get the correct answer from a search. For the "modern" stuff, I get five different answers and none of them solve my problem because the language and/or framework has changed as many times since the question was first asked.
That said; given the choice I'd choose Clojure and ClojureScript. It's a young pup compared to PHP and JS but it's still based on a 60-year old language and just a joy to work with.
> More common, especially in the West, are those who see it as a socially progressive trend of thought that affirms the power of human beings to create, improve and reshape their environment with the aid of practical experimentation, scientific knowledge, or technology. From this perspective, modernism encouraged the re-examination of every aspect of existence, from commerce to philosophy, with the goal of finding that which was 'holding back' progress, and replacing it with new ways of reaching the same end.
https://en.wikipedia.org/wiki/Modernism
Seems applicable to "modern, readable syntax". It means not afraid to buck what people are used to if there's a better way to do it.
each planets() (planet){
println("Hello " + planet + "!");
}
I bet that the double () () here leads to some pretty unreadable code outside of this happy path. But that's just my opinion, not some objective truthIn general if I read any of those descriptors without the associated comparative object, I categorize them as advertising weasel word for whatever X is being described as having them.
Those words are comparison words. They are meaningless without either benchmarks, or direct comparisons with other items in the IT landscape that have well known quantities for X.
So plop modern on that pile as well. It's even more of a loaded term, because "modern" implies a range of component interpretations, from trendiness to actual advancements in the "state of the art" (such as UTF-8 vs ASCII).
The market for music has been saturated for even longer. But nobody really complains when someone wants to start a band, because we recognize that creative work has intrinsic value, even when it produces little economic return, even if only by enriching the life of its producer. Experimenting with programming languages is only very superficially different from that.
PL are evolving continuously on some level or another. New languages pop up with new concepts or ways to do things, old languages can then incorporate some of these things.
It works with all things similarly, there is a conventional main-stream that most adhere to, for efficiency gains but the creative risk takers are the ones who drive things forward, or fail to do so.
These are usually linked with one or several languages springing out and becoming popular, either by embracing new paradigms or by strongly rejecting a mainstream concept whose usefulness have been cast into doubt either by the industry or the community.
Outside these periods, the overall interest tends to stagnate while the newer languages become stable and gain mainstream acceptance. In such a situation, a new contender must either find itself a niche, by covering very specific needs in very effective ways, or it must compete in mindshare in order to justify itself among the newest big names (i.e. any new compiled, strong typed and memory safe language nowadays is almost forced to compare with Rust and I dare say Zig).
That's what I meant with "market saturation": I think the stagnant landscape of the 00s, dominated by classic, OOP languages, gave birth to a huge wave of "safe" languages in the 10s, many of which share similar concepts and ideas; I think we're now in a moment where some of these have made a name for themselves and have been established as mainstream, serious solutions (see Rust or Go), requiring newer contendents to be really innovative in order to capture mindshare and not be considered "me too" languages.
These are, obviously, just my two cents; I really love compiler development and language design, and I think we should always support new languages and the work people do towards the goal of designing better languages and improving existing ones.
ColorForth, for example... lets you do some of the work a parser would do, at edit time, by adding additional attributes to the source.
Verilog, Ladder Logic, and Register Transfer Language all allow the programmer to specify a flow of logic and expressions that is always running, instead of a flow of instructions that we're all used to. Mixing this paradigm with the traditional code we're used to offers powerful possibilities.
Transforming spreadsheets to a set of statements that are always executed, and vice versa could be fruitful as well.
I'm not a big fan of this. IMHO local inference is great, but it makes things so much better when function signatures are explicit.
(I mean this as a general statement. Idk how the language handles this)
Ruby, of course, technically has both as well. However, inferring a return type in a dynamically-typed language is probably less noteworthy ;)
let result = if foo > 5 {
"Big foo"
} else {
"Small foo"
};
Implicit returns are just an extension of this (it’s really just “semicolons create statements; if you don’t have a semicolon, that’s the final value of the block”). The explicit return is an actual statement that returns early.IME the holistic design works pretty well, and I think you can glue together expressions much more naturally this way. The implicit return on itself would be much more annoying IMO.
Syntax toa apply some kind of constraint to the return type would be ok.
https://doc.rust-lang.org/edition-guide/rust-2018/trait-syst...
That has some implications for how Voldemort types work that can't be replicated with anonymous interface implementations. The main one is, Voldemort types are very thoroughly sealed. There is no way to create an instance of one outside of the function where it's defined. With an interface, anyone else could come along and create their own implementation.
Not true in Rust, because the type returned by the function is a concrete type.
I think we may be overloading “name” here, a bit. If you return an “impl” in Rust, you have to name an interface, but you can’t actually create new instances of the concrete type returned by the function.
I think that's the distinction that really matters. The thing that's neat about Voldemort types, and gives them their interesting properties that are distinct from anonymous types, is that they have no name at all. Not even an interface or trait name.
I don't really intend to say whether they are better or worse than other ways of accomplishing similar things. Just that they're different.
Note that interfaces in Rust are not types, they do not name types. A value cannot have interface type. What happens when you return an “impl” type is that you return some unspecified type, but that type must implement the specified interface.
You could translate the D example to:
trait HasGetValue {
fn get_value(&self) -> i32;
}
fn create_voldemort_type(value: i32) -> impl HasGetValue {
...
}
The “create_voldemort_type” function simply returns a value of unspecified type. As far as I can tell, this is equivalent to the D code, except it doesn’t use type inference for the function type.- a `Box<dyn Trait>` is like a Java object referenced by an interface. Everything uses dynamic dispatch, and it is an actual concrete type (though the actual "underlying" type is type-erased).
- an `-> impl Trait` is an existential type which uses a trait as a bound, which should be equivalent to D's "Voldemort" types, except that it can still satisfy Trait requires for other functions. For example, if you `-> impl Iterator<Item = u32>`, you can pass that result to a function expecting an iterator of `u32`. However, the type is fully defined by the callee and can't be instantiated/inspected externally.
Once the code is well defined I then start adding the type annotations , often times using the ide tooling to auto generate them.
I think a good balance would be that all published packages should have type annotations added in their source code for clarity while short scripts could do without them.
Whole form type inference is actually a super power that allows for writing extremely expressive code without giving up type safety.
However, it might be what I’m used to.
Do you think this would be less of a problem with editor/IDE support? Using colors, or other visuals, to infer and show the types while editing might overcome this desire.
With function signatures, I think it's particularly relevant for documentation as well. I should be able to look at a function signature in generated docs and know everything about what goes into and comes out of that function. For me the signature should be a complete contract, and changing code within a function body should not be able to change the signature.
I was 100% sure this was the gnome thing.
For example, OSRAM unambiguously means "I will shit" in Polish, and despite that, it's one of the most popular lightbulb brands in Poland.
Everyone knows the name Charles Darwin. Now ask them who Alf Wallace is, and why they should they should care. Or Yohan Blake. Or Atty Osborne… and so on.
Where’s the advantage in being an also-ran? It’s a tough but necessary lesson to swallow: accept your lovely technology is just too late to be the successful product you hoped for. Treat the whole exercise as a valuable learning experience, and move on to your next Big Idea as quickly as you can, this time, of course, making sure to get to market before your direct competitors can, so as to leave them in the dust instead of you.
--
† Their website doesn’t say. Just a technical features list, and yep, one look: Rust, but a lot less mature with waaay less ecosystem and support. That’s not a winning proposition; that’s a sucker’s deal. Damn but every computer geek needs to do a marketing course.
I have not seen any shred of competition suggestion here. But even if there is competition, that should be encouraged - competition is healthy, and it promotes development. Who is to say that Rust has all the good ideas and nobody else can have any good idea ?
You should encourage competition not stiffle it "because somebody else is more advanced"
> A memory model and first-class regions which together make it as fast and safe as Rust, without the borrow checker and its aliasing restrictions.
(https://www.reddit.com/r/vale/comments/i95ue8/use_case_of_th...)
Indeed. I think it’s telling that the opening question on that Reddit thread identified the exact same problem I did, and three months on they still haven’t fixed it. Programmers who fail to provide their users’ needs simply because it doesn’t interest them to do so is a cancer throughout this industry and culture. As a 30-year computer user myself, that is an attitude I expect from children, not from supposed “professionals”.
I don’t care how nice your language is, I care about whether using it is going to make my life better… or worse.
If folks here think I’m bad-tempered and unforgiving, then consider I’ve had to spend the last 20 years teaching myself to program just so I’d no longer be at the complete mercy of those “real programmers” who one too many times made my life shit.
In turn, I’ve done my share of making users lives both better and worse, and while I accept that shit happens I don’t tolerate me messing other people around any better than others messing me.
It’s meant to be called “Computer Science” for a reason. And you should see the size of the knives that real scientists bring to a science fight. I’m being kind.
A good parent fights tooth and claw for their child, and gives it every opportunity they possibly can to enable it to succeed. (And isn’t afraid to show tough love either when that is what is needed.)
Sitting with your thumb up your ass expecting success will come to it automatically just because it’s deserving is the mistake I made. And that was no minor me-too indulgence; it was a once-in-a-lifetime opportunity to put end-user automation into the hands of millions. And all that investment and future potential crashed and burned at the very last step to mass adoption, because while I was a good programmer I was a terrible product manager.
Any idiot can write code. That’s the easy part. It’s all the other stuff you need to work hardest at if you’re to build a successful market. ’Cos if you fail to put bums on seats, the best code in the world ain’t worth squat.
So once again: What is Vale’s USP†? Because it isn’t on their frontpage; which it would be had they thought to ask that question of themselves.
--
TL;DR: Goddamn it, all you noobs, but learn How To Sell already. Some of us are just too damned old and tired to want to hold your diapers till you learn to grow up. Try making our lives a bit easier for once; not just to benefit us but your own products as well.
.
(† And if you don’t even know what “USP” means, well there you are. As someone who has already tried to bring truly groundbreaking new tech to market and flubbed it, and is just about to roll up sleeves and try, try again, I’m not asking these questions just to be obtuse but to be helpful. However, if you’d rather just insult than ever amount to squat then by all means carry on.)
Look, if they just want to be another D then by all means have at it. It’s great that they have a personal hobby, but at least have the good grace to put up a notice saying they’re making this thing to please nobody but themselves. That way anyone else looking at it knows not to invest their own time into a toy project that doesn’t even take itself seriously, never mind have the chops to make the rest of the world believe in it too.
Oh, and by the way, I’ve said nothing about Vale that I’ve not said of my own projects… right before I’ve pulled the plug on them for failing to hit their overall objectives. And the best of that work’s been technically excellent, with sitting users royally pissed that I’ve just chucked their investments on the scrapheap along with my own. But I’m a realist; and it’s better to pull the trigger now and move on ASAP to the next thing, than drag out a slow but inevitable death and then have to junk an even larger investment further down the line. See also: sunk cost fallacy.
You want to ask other people to believe and invest in you? You’d damn well better bring more than just a tick list of features and your delicate feels. Else you’re just messing them around for your own personal ego.
/fin
--
TL;DR: When someone offers you difficult questions and brutal honesy, take it and ask for more, ’cos that’s the best gift they can offer. But if all you want is a pat on the head, then go ask your mom as I’m sure she thinks everything you do is wonderful.
I argue that, for most small projects, the net educational value of bringing an idea to fruition in public is far greater than any negative externality that such a project imposes. Imagine a world where every side project by a novice engineer is ruthlessly speared to the point of abandonment. I'm sure your counter-argument is that "trial by fire" is the most effective means of growth. You probably believe that forcing people to give up leads them to consider bigger and better things. This is, by far, not the case. Most people give up, and never come back. You are focusing far too much on the immediate value of the project, and completely dismissing its value as a means of creating a motivated, learned individual who can potentially make huge contributions in the future.
Anyway, if your most salient point is "the author should be more explicit about a support plan", then I don't disagree. But man, you could have presented it in a much more accessible and succinct way. If you really care about people taking your advice to heart, you have to be more kind.
- OS
- Embedded
- Game
In some respects, these are pretty niche and there is a lot of value imo in having a single-language that spans them rather than creating an even more niche language for each one. I saw C++ and now Rust filling that role.
I've not dug into the implementation details enough to confirm but when I see comments about runtime checks, I'm concerned there might be things going on that make this cover only one domain rather than covering these related domains. Even if its a matter of "programming right" to fit all domains, that just seems brittle.
EDIT: Or rather, what are Vale's selling points to someone using Rust?
Vale's main goal is to be as fast as C++, but much easier and safer, without sacrificing aliasing freedom. It does this by using "constraint references", which behave differently depending on the compilation mode:
Normal Mode, for development and testing, will halt the program when we try to free an object that any constraint ref is pointing at.
Fast Mode compiles constraint refs to raw pointers for performance on par with C++. This will be very useful for games (where performance is top priority) or sandboxed targets such as WASM.
Resilient Mode (in v0.2) will compile constraint refs to weak refs, and only halt when we dereference a dangling pointer (like a faster ASan). This will be useful for programs that want zero unsafety.
Vale v0.2 will almost completely eliminate Normal Mode and Resilient Mode's overhead with: Compile-time "region" borrow checking, where one place can borrow a region as mutable, or multiple places can borrow it as immutable for zero-cost safe references. It's like Rust but region-based, or Verona but with immutable borrowing.
Pure functions, where a function opens a new region for itself and immutably borrows the region outside, making all references into outside memory zero-cost.
"Bump calling", where a pure function's region uses a bump allocator instead of malloc/free.
[0] https://www.reddit.com/r/ProgrammingLanguages/comments/hplj2...You have constraint references, which crash the program if they still exist when the value is freed. And you have weak references, which turn into null. Constraint references can optionally be compiled into raw pointers.
Note that most of my problems could be fixed with enough tooling/editor plugins/IDE magic, I just don't use them.
main() {
something() {
// implementation block for something(), or anonymous scope?
}
something() // first or second execution?
}Why is that hard for you?
Also, as mentioned, in many languages it is valid syntax for an invocation of something, followed by a new scope.
var x = 1;
something();
{
var x = 2;
}
// x is now 1 again, as the scope
// containing the new variable x=2 is gone
something();
or, as mentioned in another comment, is it passing an execution block to `something()`? (I didn't mention that in my original since it's a bit less common of a language feature, but it's valid too)enforced semicolons and/or significant whitespace/newlines can resolve this in some cases, but it of course depends on the language. and diverging too far from common langs has its own downsides too, especially where you're changing the interpretation of what are already common patterns.
In languages like C you allocate memory yourself, and then you have to make sure to free it up too. This is error prone.
In garbage collected languages like Java there is a garbage collector who figures out when is a piece of memory no longer in use. This has run-time cost.
The third possible way is to make the ownership of that part of the memory explicit. This way the compiler can check memory safety at compile time.
There is a much better description of this in the Rust book: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.htm...
Scope-based memory management (C++) has copying overhead and destruction overhead, and a whole lot of complexity to avoid those.
Ownership-based memory management removes most of the overhead associated with copying and destruction in a less-complex fashion.
Also not every programming problem in the world is a memory-constrained hard real time problem - in fact, I would say almost all of them are not.
Memory ownership is certainly an interesting thing to have in your arsenal, but a whole class of problems (especially anything with a graph or modestly complex interlinked data structure) can't easily be reasoned about using this model, or you end up copying stuff anyway to get around the borrow checker.
Having said all that, I'd really like to see a language that lets you use a garbage collector 99% of the time for ease of use, but allows you to use other models for the small sections of the code which need it or within certain designated real time threads.
Correct... I'm a Pascal programmer. I create objects when a program starts, delete them at the end. I've never really had any of the pointer problems that seem to plague C/C++.
A lot of programs written in C and C++ are very dynamic, and may need to massively increase or decrease the number of objects they use while running. For example, consider an audio workstation: when you first start it, it has very little to do, but the user can add literally hundreds or thousands of tracks, each with dozens of effects and modifications.
Each of these features is composed of many objects. Ownership comes in when these objects have complex relationships, where one object may need to be referenced by many others. When it is time to clean up some of these objects — for example, when a user deletes a track in the audio workstation — you have to know which of these shared objects to delete. Ownership is one way of modelling that problem.
Languages that talk about ownership try to optimize the "best case" where an object has exactly one owner, because then it's very simple for the compiler to free that object. The trick is that you have to prove that no one else is using it, or else you have memory errors.
Then I guess you should instead say you’re a ‘single-shot, compiler-like batch job executable’ programmer. I imagine that colours your perception more than the choice of programming language itself. If you were to write a program that has to manipulate deeply nested data structures, or a long-running service that has to acquire and dispose of an unbounded number of various kinds of resources over the course of its runtime in a deterministic manner, you’d start longing for an ownership system very quickly.
I'd really like to understand this... what specific type of thing were you doing when you found yourself longing for an ownership system?
[edit] It occurs to me that anytime I have objects, they eventually trace their ownership to a single global variable. Perhaps that has something to do with this?
So, in general, if something is too big to fit to easily be copied, or stored in a single variable of known size (like strings, dictionaries, lists, trees, etc.) its either GC or ARC to the rescue?
That's not quite it. It's more about compile time vs runtime. Arc and GC are runtime tracking of "when can this be freed." Ownership is compile-time tracking of the same. Rust has refcounted types in its standard library, but you only need to use them relatively rarely. Here's a small example with not too much syntax. The core idea:
fn main() {
// String is a heap allocated, mutate-able string type
let s = String::from("foo");
} // s goes out of scope here, and so will be freed here;
// the compiler generates the code to free the heap allocation at this point
s is the "owner," and there's only one, so this can be tracked fully at compile time.Onwership can move, too. Let's introduce a function:
fn foo(bar: String) {
// body would go here, elided to keep this simple
} // because bar is of type "String", this function "takes ownership" of the
// value passed as 's', and so the compiler will generate the code to free
// its heap allocation at this point
fn main() {
let s = String::from("foo");
// we pass s to foo...
foo(s);
} // ... and because we did, it no longer exists in main, and the compiler
// knows this, so no code to free the allocation is generated here. if
// it were, this would lead to use-after-free.
All of this is trackable, 100%, at compile time. So there's no runtime overhead here at all.But imagine that foo() creates a new thread inside of it, and we want to access said string from inside that thread, as well as from within main. Now we have multiple owners, not a single one, and it's not possible at compile time to know when the string should be freed. The simplest solution here is to reach for reference counting, and you'd end up with Arc<String>, that is, a String that's wrapped in an atomic reference count.
(Also, references don't take ownership, so if you didn't want to have foo free its argument, you'd have it accept a reference, rather than a String directly. This would let you temporarily access the string, without freeing the backing storage when the function's body has run its course.
Real men don't use pascal ;)
Have you ever closed a file?
You worry about ownership if you have a resource and need to think of when to free/dispose/close it.
You worry about ownership if you have an object that many have a reference to, and some of the references shouldn't keep the associated resources alive, and some other of the references should keep it alive. Those that keep things they referenced alive are (shared) owners.
In Python, you might have a `list` that you built up and them pass its data to multiple functions. You assume these functions are do not mutate their arguments and you keep passing the same `list` over again. In this case, the caller is controlling the invariants of the `list` and therefore "Owns" it.
Deep down, one of those functions needs to mutate a `list` to do its calculations. Where the mutation is happening, it assumes it is the "Owner" and can modify the `list` without violating any invariants. Unfortunately, a "Borrowed" list was passed in and now the caller's invariants are violated.
Rust's ownership model helps to catch this kind of problem at compile time. By default, everything is single-owner though you can change that with a `Mutex`.