Borrow/move/closure symantics are driving me to my wit's end

The borrow/lifetimes/ownership (without runtime overhead) is the feature of the language. The other bells and whistles are the sugar on top, although you may disagree with that. Turning off the borrow checker and basically making it a cleaned up C/C++ dialect is not the point of the language.

If lifetimes/ownership are causing problems, you can certainly go about it differently than turning off the lights entirely. Use more clone, use interior mutability types, use ref counted smart pointers, put things on the heap, and so on. Heck, use unsafe code just in those places where the lifetimes get hairy. Ask for help here, stackoverflow, Reddit, etc (Rust has a great newbie friendly community). Then as you ascend the learning curve, reduce/remove those crutches. But don't toss the baby with the bath water :slight_smile:.

7 Likes

[quote="vitalyd, post:21, topic:12025"]The other bells and whistles are the sugar on top, although you may disagree with that.
[/quote]

yes: We could retrofit safety to C++ through using a static analyser, and we could use a 'smart reference' ref<T,N> for labelling lifetimes. for me, the bells and whistles are THE point.

https://www.youtube.com/watch?v=TH9VCN6UkyQ&list=PLmV5I2fxaiCKfxMBrNsU1kgKJXD3PkyxO - this is a great talk I agree with 95% of.

what we can't retrofit to C++:

  • getting rid of the header files
  • context free grammar. C++ can't find definitions out of order. It needs the header inclusion order to disambiguate syntax. The way classes and headers interact is horrible.
  • nicer lambdas (|x|..) for a single expression is awesome, we have to say [&](auto x){return ...}. Lambdas are so important for writing parallel code. They're in rust from the outset, they're a retrofit to C++.
  • immutable by default (we've got to write const everywhere, and sometimes label 'restrict' to tell it 'no aliasing' for maximum performance)
  • tagged unions (enum/match in rust is pretty cool, cleans up messaging systems etc)
  • sane/powerful macro system (in turn very useful for compile-time reflection.. serialisers)
  • There's also the issue of closed classes, my main request for C++ is UFCS, which the (annoying) committee rejected. Rusts separate struct/trait/impl idea is closer to this. (pretty much, extension methods).
  • expression based syntax (we can hack it with lambdas but it's fugly, {.... return x}() and generates more work we hope the optimizer can figure out)
  • propper tuples (because of its use of the comma), it's great being able to group values without having to introduce a named label, e.g. "pair()"
  • (the sigils ~T, ~[T] were also a big draw , and the fact they got removed is why my take on rust is more ambiguous than when I first saw it. the talk I link explains why they're a bigger deal than you might think.. wrapping types in angle brackets to do basic things is distracting.. in plain C we could just write * everywhere, adding a new inbuilt pointer type and making useful was perfect IMO.)

I am a fan of some of rusts restrictions, eg. "globals = unsafe" is great for multithreaded code.

these are the reasons I came to rust, and I suffer the hinderance of the borrow checker/safety to get them

Use more clone,

Absolutely defeats the purpose of using Rust or C++ in the first place. This has a severe performance/memory overhead.
You'd be better off using a GC'd language than using C++ or Rust in that manner.

use unsafe code just in those places where the lifetimes get hairy

no; the raw pointers are too much.
There's a middle ground where C++ references are 'safer than *', and communicate enough to provide a benefit to using them, but not requiring the overhead of further markup. a '*' can mean absolutely anything. an '&' narrows down the meanings far enough to help alot. In most code your returned references are short-leveed accessors, and your input references are non-escaping temporaries. Any more complex pointer use can be encapsulated (not so far from the unsafe block idea)

Also, rust's unsafe code is syntactically inferior to C/C++, which defeats the goal of 'something cleaner'; the syntax space is taken up on the safe focus. e.g. is a safe operator , you have to lookup some other named operation to do unsafe indexing or offsetting.

There are those of us who are actually happy with most of C++ semantics (we accept the performance/danger tradeoff), we're just burdened with beyond that with horrible syntactic cruft.. the need to keep 2 locations in sync (source/header).

"But don’t toss the baby with the bath water "

As the other person points out, there are cases where the safety simply isn't a win. You have to test in other ways. e.g. a mesh datatype has indices.. those indices have to pass various logical tests to actually represent something correctly.. if they get that far, the chance of 'out of bounds' is minimal (that would show up instantly as a glitch). So you'd certainly enable a 'bounds check' as one of many other tests in a 'debug build' (does this polygon mesh have integrity.. are there any duplicate or degenerate triangles.. does the output of the clustering match the input.. etc), but you'd get rid of it in 'release'

In time there might be further assists in the Rust ecosystem, I'm sure there's a case for an 'opposite of static', also IDEs might be able to tell you how to markup from given examples. Until then as he says it's maybe not worth the effort, which is leaving those of us who DO want a C++ replacement with a dilemma and wasted opportunity... 99% of the work done in the Rust ecosystem is useful to us, it's just 1% that needs an option to disable to make it an unambiguous 'win'

1 Like

@dobkeratops what other languages have you considered? D, Nim, and Crystal seem like reasonable options if your ideal is to write high performance code with better than C++ syntax.

I'm a bit suspicious of D because it's built around a garbage collector first, (then had the option to work around it) , whereas Rust is built around move-semantics from the ground up;

Nim again is GC based,

crystal .. I haven't heard of it (let me look for it)

  • EDIT - googling, looks interesting but it seems Crystal is another GC'd language, just non-dynamic - perhaps in a similar vein to Go ; that'll be why I haven't heard of it.

I've dabbed with a few other languages aswell, but none that are potential C++ replacements (I did find after using Rust , haskell started to make more sense, thats something I do want to get used to as a complement to the C++ use case)

I had a personal wishlist from various experiences and saw most of what I wanted in Rust.

JAI has the right stated goals, but he doesn't like "dot-call" a.foo(b) syntax.. and its going to take a while for his language to be released, and get the tooling. Rust is getting exciting again with intellij support having appeared , with the all-important 'dot-autocomplete'

I had a bash at rolling my own, it works but I can't build a surrounding ecosystem single handedly (e.g. I don't have IDE integration.) My idea was for a SPECS like approach of making something transposable, but again I didn't get as far as realising that https://github.com/dobkeratops/compiler

I've heard this line of thinking before, and I don't think it's practically possible - there's too much existing code that would need to be marked up/rewritten. At that point, you may as well (re)write in a different language.

I meant as a way to ramp up on the language, not as a way to develop "real" code. Also, cloning an Rc is cheap and allows multiple ownership for those cases where arranging otherwise is too problematic/unclear.

Not if you use them sparingly. It's certainly no worse than C++, which is raw pointers all the time. References just ensure non-nullness, but do nothing for lifetime safety.

there’s too much existing code that would need to be marked up/rewritten.

Surely thats not worse than rewriting it in an entirely new language

At that point, you may as well (re)write in a different language.

... if there's a draw like the other sweeteners

Also, cloning an Rc is cheap

if you mean 'use more Rc' fair enough, but I would still expect to only use a ref-counted object at the same times I would use a ref-counted object in C++.

Not if you use them sparingly. It’s certainly no worse than C++, which is raw pointers all the time. References just ensure non-nullness, but do nothing for lifetime safety.

There's a sliding scale between safety and level of markup; the C++ references are in a sweet spot
Anything more complex than 'inputs, don't escape' and 'temporary accessor' is when you should probably revert to raw-pointers (emphasise the danger) but then you'd be most likely doing something you can encapsulate in a class

Why is it people like you don't accept this????

we don't need safety everywhere
we do want something with less syntactic cruft than C++

99% of the rust source base is useful to us. For the sake of a simple option, Rust would be unambiguously useful to us. As it stands we're stuck with this ambiguity... do we just stick with C++ and wait for modules, concepts (very likely) and keep arguing for UFCS (seems harder to get a choice like that than an additional option in a relatively young language.. we're only fighting a few years of momentum here rather than decades of culture in C++)

have you watched jonathan blows talks about the specific use case of gamedev.
from his POV he explains that even dynamic allocation is sometimes not-performant enough, hence the more pervasive use of raw pointer tricks in setting systems up,
but also he explains the prioritising of the conceptual load. In his first video he says something that resonates 100% with me about the value of rusts old ~T (he propose *!T for his language) , that basically with std::vector and unique_ptr/ Box we're taking a huge read/write ability hit, changing a nesting level for something basic. He goes on to detail more about the need to change syntax as code moves between use cases.

he's gone on to spend x years working on this JAI language.. personally I think it's a waste for the world to have 2 c++ replacements. we just need one, with an option for unsafely. then we can share the heavy work on the toolchain/ecosystem. It's taking years for IDE support to flesh out and stabilise. For the sake of arguing against a simple option to disable something , you're inflicting this much extra work on us

To be clear, I accept the overall sentiment you have with regards to C++ and wanting a "cleaned up" version of it. This is nothing unique and many people share that. A similar sentiment exists for C, albeit less about syntax and functionality hygiene, but more for a "friendly C" subset. See Proposal for a Friendly Dialect of C – Embedded in Academia and then The Problem with Friendly C – Embedded in Academia as examples.

What I don't accept, or rather, would hate to happen is for Rust to be that cleaned up version at the expense of its core principals. As @dtolnay mentioned, there are other languages that somewhat attempt to address the part you're interested in.

By the way, you've not yet identified what exactly causes you headaches in using Rust as it was intended. We keep talking about its safety as this giant burden without anything concrete. It's true that some situations can get hairy, but if an outsider were to read this thread they would think it's a constant nuisance and barrier to progress. That may be true for someone just getting started with Rust, but I'm not sure people experienced with it would agree.

6 Likes

" Rust to be that cleaned up version at the expense of its core principals."

but if it was an opt-in, clearly marked, unsafe code would not pollute the safe ecosystem.

the unsafe code will still exist.. (e.g people continuing with C,C++, JAI..) .. by allowing this in rust there will still be more 'mindshare' for its safe parts generated, and even predominantly unsafe projects will still have safe subsets

for reference see this suggestion for crates.io, what if crates.io could tell you which crates are 100% safe code - Crates.io.. search by traits, & safety? ;

As @dtolnay mentioned, there are other languages that somewhat attempt to address the part you’re interestedin.

2 of those are completely unsuitable, they are GC based. D has an option to work without it, but the fact it started out with GC rather than 'move-semantics' also makes it far less interesting to me.
Rust has more features on my wishlist than D. I'm a big fan of 'expression-based syntax', 'immutable by default'. The overall feel (which i describe as hybrid OO/Functional) is just right

Out of curiosity, what are you developing where GC is a complete deal-breaker? All three languages I mentioned outperform Rust, sometimes significantly, in benchmarks. For example these.

Might we be participating in an XY problem? Instead of debating whether Rust should allow opt-in blanket unsafety, might it be better to discuss how to make safe code easier to write? I still haven't heard any concrete examples of what you find difficult in doing that at present. I don't doubt there's something, but let's talk about that.

Making safer code easier to write/express is firmly in everyone's interest, whereas allowing more unsafe code is not.

1 Like

Out of curiosity, what are you developing where GC is a complete deal-breaker? All three languages I mentioned outperform Rust, sometimes significantly, in benchmarks. For example these.1

As I understand GC might 'outperform' non-GC by wasting memory.

Out of curiosity, what are you developing where GC is a complete deal-breaker?

My background is gamedev. My 'main language' must be able to have handled situations I've dealt with on consoles. I don't really trust the idea of a program that expects another process to go cleaning up after it; I like to be able to reason explicitely about allocation and how memory is traversed.

The talk I linked above by jonathan blow is great; the way I explain it is
gamedev wants
performance > productivity > safety

rust seems to be targeted as
safety > performance > productivity

I think Rust actually wants safety=performance=productivity. Afterall, its slogan is essentially "fast, reliable, productive - pick 3" (source: Rust's 2017 roadmap | Rust Blog). It's just a lofty and hard goal to achieve, but it's something that everyone in the community is well aware of and working towards (as far as I know, at least).

This is why it's more productive to talk about how to achieve that, and a good start is itemizing the existing gaps. Otherwise, I think we'll keep going in circles and not advance the conversation :slight_smile:.

i'd classify that as marketing hype. Reality is a tradeoff, you can always get more of 2 by trading off the 3rd; rusts safety and performance is paid for by decreased productivity.

The tradeoff I'm after is where i can write experimental code that might crash but i can test ideas quickly, then debug when I know what I want. Safety and productivity requires extra markup.

Also there are sometimes other forms of 'correctness' that require tests, and if you pass those tests , you know the other errors are 'highly unlikely'.

Thats' why paying for rusts guarantees isn't always a win.

(i keep talking about indices for meshes. if your polygons are correct , its highly likely they're in-bounds aswell. an out of bounds index would be a visual glitch. as such the other testing you're doing is reducing the needs for bounds checks.
after manipulating the indices .. output of clustering, whatever , you usually want to do something to verify it was done right
).

but this is why I'm so enthusiastic about the syntactic tweaks; they're genuine improvements over C++ where we know C++ has syntactic cruft (can't return multiple values easily, are wasted on something useless, need to write 'const' so much. rust IMO has a more efficient syntax, partly 'paid for' by a couple of extra keywords (let, fn..) which is a good tradeoff . C/C++'s use of the "," is designed for it's 'for (..;..;..) loop' , but once you have 'for ..in..' and ways of iterating with lambdas you don't really need that)

I believe it is essential complexity;

"safety performance productivity - pick3" is claiming too much; rust pays for safety and performance by front-loading a lot of issues. There are scenarios in which its choices don't help. options to loosen things (keep the defaults as they are) would help.

There are cases where the use of references has simple intent behind it , but it can be complex to figure out how to explain this to the compiler.

There are cases where what you want to do is simple to express through operators, but you have to look up a named helper function to do the same thing - this is an awful process to have to go through manually. ("i want to do X,Y,Z.. But I can't just write X,Y,Z. I must scour the documentation to discover the name 'W' which is equivalent.." ... the total vocabulary that you must learn to do anything is higher.)

Rust would only be an unambiguous productivity win alongside a code-discovery tool where you can write a code snippet and it will tell you 'this is the equivalent safe named function in the std::lib'.

This is where C/C++ can end up being more productive: when you don't know the name of the 2line function, you can just write it. it might be unsafe, but it slots into other behaviour that you must write tests for anyway, and if it passes those tests the chances it was safe are might higher.

What I could get from your posts so far is that you find Rust unproductive and hard to use because you don't seem to be familiar with it enough.

This is also boils down to familiarity with the language. If you work ie. with Haskell just enough you realize that testing out ideas can be no more than juggling with the type system, the same is true with Rust.
Throwing Rc at points where you just want to experiment, than swapping out them to & and &'a is just like that.

Again, if you get familiar with the language than writing that 2 line safe function is going to be as easy as writing that in C++.

Very dismissive, you have missed my point.

Again, if you get familiar with the language than writing that 2 line safe function is going to be as easy as writing that in C++.

but it really isn't.

The extra safety isn't free. It's a tradeoff. There IS , objectively, more markup required to make things 100% safe.

The need for 'split_at_mut'... the fact it exists at all, - those use cases are handled trivially through basic operators in C++.
you need extra markup and vocabulary to write in a 'compile-time-provably safe' way

My assertion is that when you combine unsafe code with complex tests (which you need during experimentation anyway), the extra markup for compile-time safety isn't always a win.

Throwing Rc at points where you just want to experiment, than swapping out them to & and &'a is just like that.

Highly un-ergonomic.. big syntax change. See jonathan blows excellent videos for the rationale around this issue.
(The language went backwards on this in the time I've followed it - it was a lot better with the sigils.)

I'm thinking mostly about passing temporaries around which would never be anything other than references in c++, and they do sometimes need additional lifetime markup. you're suggesting something with a runtime+memory cost for that?

If you work ie. with Haskell just enough you realize that testing out ideas can be no more than juggling with the type system,

Not everything can be expressed in the type system . We have a long way to go. Just about anything I'm interested in requires writing extra debug code to visualise the results or intermediate states to make sense of what is going on. The type system can't tell you if the camera is the right way up , why this object moves the way it does, how efficiently some clustering scheme has worked , etc etc.

No, it's not free. It has just as much cost as it takes to remember many of the UB in C/C++ to write your code in such way that it won't blow up, or looking at the docs to figure out whether that function parameter will be mutated or not, should you copy your buffer or not. Be it even a simple experimental code. Sure, when you know it would not blow up but rustc still argues with you, than yes, I understand your point.

For testing purpose, yes. If you just test out a some code and don't want to figure out where and (and mostly) why you need lifetimes for calling x,y and z, then passing a cloned Rc is way easier.

True.

having come from asm and C I don't think of C++ UB as really unnatural, it all flows logically and I think of each language as a code-generator for the previous 'level'.

[quote="zen3ger, post:38, topic:12025"]or looking at the docs to figure out whether that function parameter will be mutated or not, should you copy your buffer or not.
[/quote]

I definitely appreciate rusts more solid immutability (thats one of the things that does keep me coming back) but I find C++ programmers do try to make full use of const to tell you what will or wont change .. it's not so far off and basically a flip of default .. mut / const. (I do also like Rust making globals unsafe)

.. exactly, when doing things which you know work from past experience .. or you have a big picture in your head of how it will go.

I do realise how better type systems can help you piece things together.. I'm not objecting to that, just the loss of the shortcuts; thats why I want an #[unsafe] option.

What I like about rust is the cases where it makes things I already know about easier; (inbuilt tagged unions, 'immutable' is a better default, expression-based syntax makes writing everything 'initialised' easier, and I do definitely want a way of saying a function wont touch globals in c++.

I can still state that it all boils down to how familiar the programmer is with the language he/she works with.

Your mental model of the program you want to write is just not, i guess, rusty enough? Probably someone experienced with rust can write what you want without opt-in unsafe, just like you would write the right code in C++, because of past experiences.
But I'm sure you would end up with two very different solutions thanks to how the languages work.