[HN Gopher] A comparison of Rust's borrow checker to the one in C#
___________________________________________________________________
A comparison of Rust's borrow checker to the one in C#
Author : SkiFire13
Score : 304 points
Date : 2024-10-27 15:25 UTC (4 days ago)
(HTM) web link (em-tg.github.io)
(TXT) w3m dump (em-tg.github.io)
| bob1029 wrote:
| > Maybe I'm bad at searching for these things, but these changes
| to C# seem to have gone completely under the radar in places
| where you read about memory safety and performance. Maybe it's
| just because the language additions have happened super slowly,
| or maybe the C# and Rust communities have so little overlap that
| there aren't enough people who program in both languages to
| notice the similarities.
|
| If you are looking at this through the lens of HN, I think much
| of this can be attributed to a certain ideological cargo cult
| that actively seeks to banish any positive sentiment around
| effective tools. You see this _exact_ same thing with SQL
| providers, web frameworks, etc. If the tool is useful but doesn
| 't have some ultra-progressive ecosystem around it (i.e., costs
| money or was invented before the average HN user's DOB), you can
| make a winning bet that talking about it will result in negative
| karma outcomes.
|
| Everyone working in enterprise software development has known
| about the power of this language for well over a decade. But, you
| won't find _a single_ YC startup that would admit to using it.
| zdragnar wrote:
| Well, common lisp and some schemes and prolog tend to get a
| good deal of praise here, even if their commercial utilization
| is lower. OCaml, F# and Clojure tend to get a good deal of
| favorable comments as well.
|
| I suspect it is less about cargo culting, and more about two
| separate things:
|
| First, the tooling for C# and really anything dotnet has been
| awful on any OS other than Windows until fairly recently.
| Windows is (to be blunt) a very unpopular OS in every
| development community that isn't dotnet.
|
| Second, anthing enterprise is worth taking with a skeptical
| grain of salt; "enterprise" typically gets chosen for
| commercial support contracts, vendor lock-in, or astronaut
| architects over-engineering everything to fit best practices
| from 20 years ago. Saying big businesses run on it is a virtue
| is akin to saying that Oracle software is amazing or that
| WordPress engineering is amazing because so many websites run
| on it. Popularity and quality are entirely orthogonal.
|
| I suppose there is probably another reason, which is the
| cluster fuck that has been the naming and churn of dot net
| versions for several years. ASP.NET, then core, then the core
| suffix got dropped at version 5, even though not everything was
| cross platform... So much pointless confusion.
| devjab wrote:
| > Everyone working in enterprise software development has known
| about the power of this language for well over a decade.
|
| I think it depends on location. In my part of the world .Net is
| something which lives in middle sized often stagnating
| companies. Enterprise around here is married to the JVM and
| they even tend to use more Typescript on the backend than C#.
| I'm not going to defend the merits of that in any way, that is
| just the way of things.
|
| There being said I do get the impression that HN does know that
| Rust isn't seeing much adoption as a general purpose language.
| So I wouldn't count C# out here considering how excellent it
| has become since the transition into Core as the main .Net. I
| say this a an absolute C# hater by the way, I spent a decade
| with it and I never want to work with it again. (After decades
| of SWE I have fun with Python, C/Zig, JS/TS, and, no other
| language.)
| DeathArrow wrote:
| > Enterprise around here is married to the JVM and they even
| tend to use more Typescript on the backend than C#. I'm not
| going to defend the merits of that in any way, that is just
| the way of things.
|
| Many developers already know Java, so it's easier to hire
| Java developers.
|
| >There being said I do get the impression that HN does know
| that Rust isn't seeing much adoption as a general purpose
| language. So I wouldn't count C# out here considering how
| excellent it has become since the transition into Core as the
| main .Net. I say this a an absolute C# hater by the way, I
| spent a decade with it and I never want to work with it
| again. (After decades of SWE I have fun with Python, C/Zig,
| JS/TS, and, no other language.)
|
| I didn't like the old C# and .NET. However, the new one is
| wonderful and I quite enjoy using it. More than Java or Go.
| On par with Python, but I wouldn't use Python for now for
| large web backend applications.
|
| I tried Rust, bur for some reason I can't grow to like it.
| I'd prefer using C or Zig and even a sane subset of C++ (if
| such thing even exists).
| devjab wrote:
| I don't like C# because I don't like the "magic" which is
| also what makes it special. In that regard I actually think
| highly of Go's more simplistic approach to everything, from
| explicit error handling to the flat "class hierarchy". Go
| isn't as good as C# for a lot of things and there are no
| technical reasons for my C# hatred. Well I guess you could
| argue that having to fight the "magic" when you run into
| things it can't handle as technical but for 99% of the
| things this isn't an issue.
|
| Python is a horrible language, but it's also the language I
| actually get things build in. I do think it's a little
| underrated for large web apps since Django is a true work
| horse, but it takes discipline. C is for performance,
| embedded and Python/Typescript libraries and Zig is
| basically just better C because of the interoperability.
| Typescript is similar to Python for me, I probably wouldn't
| use it if it wasn't adopted everywhere, but I do like
| working with it.
|
| We've done some Rust pocs but it never really got much
| traction and nobody really likes it. + I don't think I've
| ever seen a single Rust job in my area of the world. C/C++
| places aren't adopting it, they are choosing Zig. That is
| if they're going away from C/C++ at all.
| jen20 wrote:
| Where are you? Somewhere with more Zig adoption than Rust
| outside of the OSS terminal ecosystem sounds pretty
| interesting!
| devjab wrote:
| A non-Copenhagen part or Denmark, but it's really not
| that interesting. Almost no adoption isn't that much more
| impressive than no adoption.
|
| I'm fairly confident that PHP, Python, JS/TS, Java and
| C/C++ will be what people still work on around here when
| I retire. Go is the only language which has managed to
| see some real adoption in my two decade career.
| lenkite wrote:
| Zig looks to be pretty much work in progress at the
| moment, with lots of stuff broken. Even if the language
| is saner to learn than Rust, it cannot be considered
| ready for production.
| devjab wrote:
| I think it works rather well as a drop-in for C, but it's
| not like we're rewriting, or have stopped working with C.
| radicalbyte wrote:
| > I have fun with Python, C/Zig, JS/TS, and, no other
| language.
|
| Python is the least fun language currently in use at any
| scale. Pretty much completely down to the lack of a coherent
| tool chain. When JS has better package management than you
| then you know you have a massive problem.
| WD-42 wrote:
| I think it has more to do with c# being a Windows only
| programming language for the majority of its life. And guess
| what, a lot of people don't like Windows.
|
| And don't bring up mono, etc. it was a dumpster fire then and
| it's only recently gotten better. It tough for any tech to shed
| a very long negative legacy.
| Kipters wrote:
| .NET Core 1.0 was released cross-platform 8 years ago though,
| there's a ton of new devs for who it has always been cross-
| platform
| WD-42 wrote:
| And Rust 1.0 (which this article seems to be comparing
| against) came out 9 years ago. These things take time.
| palmfacehn wrote:
| Reading this thread is the first I'm learning of this. Even
| with the enthusiasm expressed here, I'm still suspicious
| that there will be incompatibilities. Microsoft has a
| history of doing things a certain way.
| DeathArrow wrote:
| I don't get why you are downvoted. It's true that some
| languages, frameworks, operating systems are more popular on HN
| that others. Reasons for this might be complex and we might
| enter into very hard and complicated sociological arguments if
| we try to discuss them.
|
| >Everyone working in enterprise software development has known
| about the power of this language for well over a decade.
|
| What is an enterprise? Is Google not an enterprise? Is Apple
| not an enterprise? Is Facebook not an enterprise? What about
| Netflix, Uber and any other big tech company? Weren't all
| enterprises start-ups at the beginning?
|
| Does enterprise mean boring old company established long before
| the invention of Internet, which does old boring stuff, employs
| old boring people and use old boring languages? I imagine a
| grandpa with a long white beard staring at some CRTs with Cobol
| code and SAP Hana.
| wongarsu wrote:
| In this context I'd interpret enterprise to mean any company
| with over 1000 employees that is not what Silicon Valley
| calls a tech company. So no Apple, Google or Uber, but the
| notable players of basically every other industry, from A for
| accommodation or accounting to W for wood paneling.
|
| But I wouldn't say their choice of C# is due to them being
| old and boring. If it was that, they'd use Java (as many do).
| In my eyes choosing C# signals to me that you do want good
| technology (again, you could have gone with Java), but want
| that technology to be predictable and boring. A decent rate
| of improvement with minimal disruption, and the ability to
| solve a lot of issues with money instead of hiring (lots of
| professionally maintained paid libraries in the ecosystem).
| Ygg2 wrote:
| > ...actively seeks to banish any positive sentiment around
| effective tools
|
| Effective at what?
|
| Want GC lang with lots of libraries? Use Java.
|
| Want GC free lang with safety? Use Rust.
|
| Otherwise just use C. Or C++.
|
| For me C# lies in this awkward spot. Because of past decisions
| it will never have quite the ecosystem of Java. And because GC
| -free and GC libraries mix as well as water and oil, you get
| somewhat of a library ecosystem bifurcation. Granted GC-less
| libraries are almost non-existent.
| DeathArrow wrote:
| I worked in both C# and Java. And other languages. Resuming
| C# and Java to "GC languages with lots of libraries" doesn't
| accurately depict neither C#, nor Java.
|
| Since we discuss C# here, it is a good jack of all trades
| language where you can do almost anything, with decent
| performance, low boilerplate. It's easy to read, easy to
| learn and you have libraries for everything you need,
| excellent documentation and plenty of tutorials and examples.
| A great thing is that for every task and domain there is a
| good library or framework that most developers use, so you
| don't have to ask yourself what to use and how and you find a
| lot of documentation, tutorials and help for everything.
|
| Java it's a bit more boiler plate-y, had a bit less features
| and ease of use and had many libraries and frameworks that
| did the same thing. Had Java been better, Kotlin wouldn't
| need to be invented.
|
| >Want GC lang with lots of libraries? Use Java. Want a fast
| to develop and easy to use language? Just use C#.
|
| >Want GC free lang with safety? Use Rust. Want a language
| which you can use for almost eveything? Web front-end, web
| backend, services, microcontrollers, games, desktop and
| mobile? Use C#.
|
| >Otherwise just use C. Or C++. Or whatever works for you.
| Whatever you like, find fun and makes you a productive and
| happy developer. There is nothing wrong in using C or C++. Or
| Python. Or Haskell.
| Ygg2 wrote:
| > Since we discuss C# here, it is a good jack of all trades
| language where you can do almost anything, with decent
| performance, low boilerplate.
|
| That's basically modern-day Java, with Lombok and other
| tidbits. Furthermore, if I recall correctly, Java has
| better performance on web benchmarks than C#.
|
| > Had Java been better, Kotlin wouldn't need to be
| invented.
|
| Kotlin was invented to make a sugary version of Java, and
| thus drive more JetBrains sales. It got popular because
| Oracle got litigious. As someone who's been on the Java
| train for almost two decades, what usually happens, if any
| JVM Lang becomes too popular, Java has the tendency to
| reintegrate its features into itself.
|
| > Whatever you like, find fun and makes you a productive
| and happy developer. There is nothing wrong in using C or
| C++. Or Python. Or Haskell.
|
| Sure, assuming it fits the domain. Like, don't use Python
| for kernel dev or Java for some obscure ML/AI when you
| could use Python.
| Kipters wrote:
| > That's basically modern-day Java, with Lombok and other
| tidbits.
|
| I wouldn't call Lombok "modern", more like "a
| terrifyingly hacky way to tackle limitations in the
| language despite the unwillingness to make the language
| friendlier" and a far cry from what source generators can
| do in C#
| xxs wrote:
| Getters and setters are a mediocre design choice, not a
| limitation. Records have existed for years, too.
| e3bc54b2 wrote:
| > Records have existed for years
|
| As a fan of Records, this is a punch to the gut.
|
| The ecosystem is years and years away from using records.
| Almost every huge monolith decade+ project is still on
| Java 8, those who moved to something new still can't be
| liberal with them, because oh look, _none_ of the
| serialize /deserialize libs can work with them because
| everything, to this day, abuses reflection for generating
| objects like a giant fucking hack it is.
|
| Apology for the rant, but I migrated a big project to 21
| early this year, am in the middle of migrating another
| 1M+ line codebase to 21, and the sorry state of records
| is such a sad thing to witness.
|
| I give a decade before records are anything but 'a fancy
| feature'.
| xxs wrote:
| It's a fair point of stuck w/ java8, yet the reference
| was about "modern java".
|
| With that said - lombok is not needed at any form there
| either, use a c-tor with fields and make the public
| final. If you have too many fields in a class, it's
| likely a good idea to split it regardless.
|
| In all cases dumb getter/setters are just public fields
| but taking more meta space (and larger byte code, the
| latter has some consideration when it comes to inlining)
|
| Also, if I had 1M LOC and my serialization/communication
| libraries didn't support whatever I've picked - I'd patch
| the libraries to support it.
| e3bc54b2 wrote:
| > It's a fair point of stuck w/ java8, yet the reference
| was about "modern java".
|
| And I'm saying that even after writing the most of the
| first project (closing in on 100kLOC now) in 21, I still
| can't have records where the make the most sense (service
| boundaries) because libs and larger ecosystem don't
| support them.
|
| > Also, if I had 1M LOC and my
| serialization/communication libraries didn't support
| whatever I've picked - I'd patch the libraries to support
| it.
|
| 1MLOC in java land is.. not unusual. And if you're
| talking about patching libs like jackson/jaxb/whatever,
| my good person, you truly underestimate how much actual
| work people have (where Java upgrade is a distant
| afterthought, I only did it because I wanted to scratch
| the itch and see how far I could push processes in my
| org), or how much impact that might have for a drive-by
| contribution. Updating such core ecosystem libs in java
| is no small feat. They are used absolutely everywhere,
| and even tiny changes require big testing. There is a
| reason you find apache libs in every single project,
| because they have matured over past couple of _decades_
| without such drastic rug-pull of a change.
| xxs wrote:
| I did say all that (incl the 1M+) stuff coming from
| personal experience. I have "fixed" all kind of libraries
| (incl. database drivers, JDK itself, PKI cert loading,
| netty/jetty, ORM providers). I'd consider jaxb/jackson on
| the easy side of things.
|
| Also I'd actively remove all apache commons as well. Even
| in Java8 most of the functionality is redundant.
|
| With all that I meant it should not be really
| underestimation.
| e3bc54b2 wrote:
| You are really part of the cream, and I mean it as an
| honest compliment.
|
| I am part of the dark matter, although self-initiated
| java upgrades already put me on the right side of bell-
| curve.
|
| > Also I'd actively remove all apache commons as well.
| Even in Java8 most of the functionality is redundant.
|
| I used to think that. Then I had to decompress zip files
| in memory and selectively process the children. Of course
| Java has the functionality covered in stdlib, but they
| require so much boilerplate, and commons-compress was
| such a pleasure that I was done in 10 minutes. The same
| goes for other apache libs too.
|
| OTOH, I wholeheartedly agree about Lombok being
| unjustified curse.
| Ygg2 wrote:
| Hack or not, it's been working relatively well for the
| past decade.
|
| But even, if you account for that, the records in Java do
| most of what Lombok used to do - make class externally
| immutable, add default `toString`, `equals` and
| `hashCode` implementations, allow read-only access to
| fields.
|
| > what source generators can do in C#
|
| Having had the displeasure of developing source
| generators in C# (in Rider), what they do is make code
| impossible to debug while working on it. On top of
| relying on an ancient version of netstandard.
|
| I cannot emphasize enough how eldritch working on them
| is. While developing, whatever change you write isn't
| reflected when you inspect codegen code, and caching can
| keep old code beyond even post re-compilation unless you
| restart the build server, or something.
|
| So whenever you try to debug your codegen libs, you toss
| a coin:
|
| - heads it shows correct code
|
| - tails it's showing previously iteration of code gen
| code, but the new code is in, so the debugger will at
| some point get confused
|
| - medusae it's showing previous iteration of code gen
| code, but new code hasn't been propagated, and you need
| to do some arcane rituals to make it work.
|
| Hell, even as a user of codegen libs, updating codegen
| libs caused miscompilation because it was still caching
| the previous codegen version.
| DeathArrow wrote:
| > benchmarks
|
| https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
| Ygg2 wrote:
| Ahem.
|
| > web benchmarks
|
| https://www.techempower.com/benchmarks/#hw=ph&test=compos
| ite...
| EraYaN wrote:
| Those are notoriously cheat-y for just about all
| languages on that list. Any actual project is never going
| to get close to the efficiency that those number would
| require. Both Java and .NET over promise by A LOT with
| the numbers they get there.
| Ygg2 wrote:
| Cheat-y in what way? I don't consider micro benchmarks
| that interesting especially since C# does have two aces
| that Java will get in mid term - namely Simd and
| primitive types.
|
| The Tech Empower benchmarks do seem to reflect general
| state of Java Web Framework ecosystem with Vert.x being
| they hyper fast web framework and Spring being way
| slower.
| EraYaN wrote:
| Well the amount of tricks that basically nobody can
| implement in production that those implementations do to
| get the numbers that they do is ridiculous. Meaning the
| numbers they get are wildly optimistic at best and
| misleading at worst.
|
| If you take the standard template for any of these
| frameworks (both Java and C# and any other language) and
| you add authentication etc, the real performance will be
| 5-10% of the numbers reported in those benchmarks. Have a
| look through some of the weirdness in the implementations
| it's wild (and sometimes educational). The .NET team
| especially has done stuff specifically to get faster on
| those benchmarks.
| neonsunset wrote:
| I second this sentiment. If anything, I genuinely think
| that the way .NET's TechEmpower submissions look does
| more damage than good. BenchmarksGame offers a much
| better close-up comparison by having much simpler
| submissions that mostly consist of code that you could
| totally see yourself write in a regular setting.
|
| .NET is perfectly capable of standing on its own, and if
| there are specific areas that need improvement - this
| should serve as a push to further improve DB driver
| implementations and make ASP.NET Core more robust against
| various feature configurations. It is already much, much
| faster than Spring which is a good start, but it could be
| pushed further.
|
| I'd like to note that neither Go nor Java are viable for
| high-performance programming in a way that C# is. Neither
| gives you the required low-level access, performance
| oriented APIs, ability to use zero-cost abstractions and
| platform control you get to have in .NET. You can get far
| with both, but not C++/Rust-far the way you can with C#.
| Ygg2 wrote:
| > BenchmarksGame offers a much better close-up comparison
| by having much simpler submissions that mostly consist of
| code that you could totally see yourself write in a
| regular setting
|
| Yeah, except if you are working on Web servers the
| quality of the framework and its supporting libraries is
| much more important than what code could theoretically
| achieve. What is the point of being able to 200 mph when
| you only ever drive up to 30mph.
|
| > Neither gives you the required low-level access,
| performance oriented APIs, ability to use zero-cost
| abstractions.
|
| Java is working on high performance abstractions, see
| Vector API (Simd) and project Valhalla (custom primitive
| types).
|
| Sure C# has a theoretical leg up (for which it paid
| dearly by causing backwards incompatibility with reified
| generics) but most of the libraries don't use low-level
| access or SIMD optimizations or what not.
| dahauns wrote:
| > Have a look through some of the weirdness in the
| implementations it's wild (and sometimes educational).
| The .NET team especially has done stuff specifically to
| get faster on those benchmarks.
|
| Could you give me a pointer or two? I wondered about that
| myself, especially considering the massive improvement
| from "old" .NET to the core/kestrel based solutions - but
| a quick browsing a while ago mostly left me astonished
| how...well, for lack of a better word, _banal_ most of
| the code was.
|
| Agreed though, lack of all kinds of layers like auth, orm
| etc. are sadly a drawback of these kinds of benchmarks,
| if understandable - it would make comparability even
| trickier and has the danger of the comparison matrix of
| systems/frameworks/libraries exploding in size. But yeah,
| would be nice datapoints to have. :)
| vips7L wrote:
| Most the Debian benchmarks for C# are cheaty too. They
| frequently just call out to C libraries rather than use
| something implemented in the language.
| neonsunset wrote:
| Not sure which benchmarks you have in mind. Could you
| provide a link to any of those? .NET's standard library
| never calls into anything C aside from kernel APIs and
| certain runtime helpers which is a given.
|
| If you meant BenchmarksGame, then it's the other way
| around - Java is most competitive where it relies heavily
| on GC[0], and loses in other areas which require
| capability to write a low-level implementation[1] that C#
| provides.
|
| The only places where there are C calls are pidigts[2]
| and regex-redux[3] benchmarks, in both of which Java
| submissions have to import pre-generated or pre-made
| bindings to GMP and PCRE2 respectively. As do all other
| languages, with varying degrees of "preparation".
|
| [0]: https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
|
| [1]: https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
|
| [2]: https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
|
| [3]: https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
| igouy wrote:
| No, they do not "frequently just call out to C
| libraries".
|
| 2 of 10 (pidigits and regex-redux) allow use of widely
| available third party libraries -- GMP, PCRE, RE2 --
| because there were language implementations that simply
| wrapped those libraries.
| xxs wrote:
| >That's basically modern-day Java, with Lombok and other
| tidbits.
|
| Lombok is exceptionally backwards. You don't need
| getters/setters; and you should know how to try hashCode
| (and equals).
|
| ...and records exist
| Ygg2 wrote:
| The last few Spring projects I worked on that used the
| latest Java, still used Lombok. Records do exist, but you
| can't or don't want to always use them.
| lmm wrote:
| > Java it's a bit more boiler plate-y, had a bit less
| features and ease of use
|
| Maybe slightly. But the difference is too marginal to
| change languages over.
|
| > had many libraries and frameworks that did the same thing
|
| Maybe, but it also has many more libraries doing the one
| obscure thing that you need for your domain.
|
| In a vacuum, C# is a very good language, probably better
| than Java (as it should be given that it was able to learn
| some lessons from early Java). But in the wider world of
| programming languages they really are extremely close to
| each other, they're suitable for exactly the same problems,
| and Java has a substantially greater mass of
| libraries/tooling and probably always will do.
| high_na_euv wrote:
| Java ecosystem is more fragmented and inconsistent than C#
|
| C# is better designed lang, has really strong tooling and
| ecosystem and well designed std lib
| DeathArrow wrote:
| > Everyone working in enterprise software development has known
| about the power of this language for well over a decade. But,
| you won't find a single YC startup that would admit to using
| it.
|
| Not sure about that. Maybe there are? If you do web or mobile
| apps, C# would be an excellent choice. Go would be also an
| excellent choice for web.
|
| For AI I wouldn't use C#. Even though it has excellent ML
| libraries, most research and popular stuff is done using Python
| and pytorch, so that's what I would chose.
|
| For very low level, I'd take C or Zig. But I don't know many
| startups who are into very low level stuff.
| jeroenhd wrote:
| C# is an incredible language with a past haunted by Microsoft
| only making it useful on Windows and expensive licenses for
| really good editors. It's come a long way, but I don't blame
| people for thinking of it as "Microsoft Java".
|
| My only issue with many of the improvements in C# is that all
| of them are optional for backwards compatibility reasons.
| People who don't know or don't care about new language features
| can still write C# like it's 2004 and all of the advantages of
| trying to modernize go out of the window. That means that
| developers often don't see the need to learn any of the new
| features, which makes it hard for projects to take advantage of
| the language improvements.
| alkonaut wrote:
| Hard agree on the backwards compatibility. It appears to be
| some law of nature that "our next compiler must compile 20
| year old code".
|
| Instead of new platform libs and compilers simply defaulting
| to some reasonable cutoff date and saying "You need to
| install an ancient compiler to build this".
|
| There is nothing that prevents me from building my old
| project with an older set of tools. If I want to make use of
| newer features then I'm happy to continuously update my
| source code.
| Clubber wrote:
| >It appears to be some law of nature that "our next
| compiler must compile 20 year old code".
|
| Some examples of companies/products not implementing
| backwards compatibility are Delphi and Angular. Both are
| effectively dead. .NET Core wasn't backwards compatible
| with .NET Framework, but MS created .NET Standard to bridge
| that gap. .NET Standard allows people to write code in .NET
| core and will run in .NET Framework. It's not perfect, but
| apparently it was good enough.
|
| Companies usually won't knowingly adopt a technology that
| will be obsoleted in the future and require a complete
| rewrite. That's a disaster.
| alkonaut wrote:
| But that's .NET, not C#. Language and platforms are
| different. _Libraries_ must be compatible (because you
| don 't know if your library will be consumed in a newer
| app).
|
| But the compiler only consumes syntax (C#11, C#12 C#13
| and so on) so I don't see why the compiler that eats C#13
| necessarily must swallow C#5 without modification
| Clubber wrote:
| They did a breaking change in a recent C# where nullable
| objects must be postfixed with a ?, so old code is:
| public Patient Patient { get; set; }
|
| The same thing with modern code would be
| public Patient? Patient { get; set; }
|
| Because with the new C#, objects are by default not null.
| Fortunately there is a compiler flag to turn this off,
| but it's on by default.
|
| As a guy who has worked in C# since 2005, a breaking
| change would make me pretty irate. Backwards
| compatibility has its benefits.
|
| What issues do you have with backwards compatibility?
| alkonaut wrote:
| NRT wasn't really breaking as it's a warning which you
| control top level. But there have been some real breaking
| changes in edge cases but they are pretty far between. I
| think the language could be better if it was always
| structured in the best way possible, rather than in the
| best compatible way.
|
| As a class library example (which is contrary to what I
| said earlier about .NET compatibility vs C#
| compatibility) is that it was a massive mistake to let
| double.ToString() use the current culture rather than the
| invariant culture. It should change to either required
| passing a culture always (breaking API change) or change
| to use invariantculture (behaviour change requiring code
| changes to keep old behavior)
| Clubber wrote:
| >a massive mistake to let double.ToString() use the
| current culture rather than the invariant culture.
|
| I would imagine that's a carryover from the Win32/Client-
| Server days when that would have been a better choice.
|
| Is that annoying? Yea. Is that annoying enough to force
| companies to collectively spend billions to look through
| their decades old codebases for double.ToString() and add
| culture arguments? Also keep in mind, this is a runtime
| issue, so the time to fix would be much more than if it
| were a compile issue. I would say no.
| neonsunset wrote:
| Nowadays you just apply https://learn.microsoft.com/en-
| us/dotnet/core/runtime-config... and call it a day. It is
| also added as a default to all AOT templates.
| alkonaut wrote:
| That's a great idea (and after the fact, much better than
| changing the API). On day 1 it should have been easy
| though.
| lomase wrote:
| You don't need to rewrite old .net project to compile it
| in a new machine.
|
| But is also not a trivial task.
| thijsvandien wrote:
| > Delphi
|
| Just the move to Unicode (i.e. from 2007 to 2009) took
| some work, but otherwise I can't think of any intentional
| breaking changes...? In fact, it's one of the most stable
| programming environments I know of - granted, in part
| because of being a little stagnant (but not dead).
| Clubber wrote:
| I seem to recall some in Delphi 4, but it's been forever.
| magicalhippo wrote:
| Ah yes, the version released in 1998. Let's ignore the 26
| years since then...
|
| I've been using Delphi since Delphi 3. The only really
| breaking change I can recall was the Unicode switch. And
| that was just a minor blip really. Our 300kloc project at
| work took a couple of days to clean up the compiler
| errors and it's been Unicode-handling ever since. It's
| file integration and database heavy, so lots of string
| manipulation.
|
| Most of my hobby projects didn't need any code changes.
|
| In fact, the reason Delphi was late to the Unicode party
| was precisely because they spent so much time designing
| it to minimize impact on legacy code.
|
| Not saying there hasn't been some cases, but the
| developers of Delphi have had a lot of focus on keeping
| existing code running fine. We have a fair bit of code in
| production that is decades old, some before y2k, and it
| just keeps on ticking without modification as we upgrade
| Delphi to newer versions.
| johnisgood wrote:
| Yeah, or you think Ada / SPARK ever comes up when people cry
| for memory safety? It has existed in Ada / SPARK for ages, but
| nah...
| failbuffer wrote:
| Alternatively (or at least additively), most C# developers
| don't really need all the new ref/Span features. They're
| writing line-of-business apps and garbage collection is a fact
| of life, not some burden to be avoided.
|
| Microsoft probably added these features to push the language
| into new niches (like improving the story around Unity and
| going after Arduino/IoT). But it's of little practical appeal
| to their established base.
| neonsunset wrote:
| What a great article, thank you for sharing it!
| zigzag312 wrote:
| I always felt like these features are adding a new programming
| paradigm to C# that allows you to bypass GC in safe code.
|
| I wish more people would talk about it. Thank you for such an
| interesting article!
| pjmlp wrote:
| Many of these features exist since .NET 1.0, given its scope of
| languages to support, including C++.
|
| So even those that weren't initially exposed in unsafe mode,
| were available at the MSIL level and could be generated via
| helper methods making use of "System.Reflection.Emit".
|
| Naturally having them as C# language features is more ergonomic
| and safer than a misuse of MSIL opcodes.
| zigzag312 wrote:
| "System.Reflection.Emit" is not compatible with NativeAOT.
|
| Using C/C++/Rust to do the same task is probably more
| productive than emitting MSIL opcodes, so that solution
| wasn't really that practical.
|
| But with these new features being more ergonomic and
| practical, it becomes cost effective to just do it in C#
| instead of introducing another language.
| pjmlp wrote:
| Yeah, but nothing of that is the point being discussed,
| with Native AOT still not available in GUI workloads.
|
| Also P/Invoke and CCW/RCW do have costs cross the runtime
| layer, even if minor when compared with other languages.
| zigzag312 wrote:
| I'm not sure I follow. Where are GUI workloads being
| discussed in the article?
|
| If anything, article doesn't talk about MSIL or CLR, but
| C# language features. CLR is not the only target C#
| supports.
|
| NativeAOT is supported in Avalonia (cross-platform UI
| framework), Razor Slices (dynamically render HTML from
| Minimal APIs) and I think there is also some support for
| AOT in MonoGame & FNA (game dev frameworks).
|
| However, it's still early and a lot of the ecosystem
| doesn't support NativeAOT.
| pjmlp wrote:
| No, neither was Native AOT.
|
| Native AOT depends on CLR infrastructure.
| Rohansi wrote:
| I believe you can avoid most of not all of the P/invoke
| overhead these days by using unmanaged function pointers
| and not using the automatic marshalling.
| neonsunset wrote:
| Whenever you use [DllImport], the analyzer will nudge you
| to auto-fix it to [LibraryImport] which source-generates
| a marshalling stub (if any is needed) that then calls an
| inner [DllImport] that does not require runtime
| marshalling. This is very cheap since function address
| gets cached into a readonly static which then gets baked
| into the machine code once the JIT produces Tier-1
| compilation for your method.
|
| On NativeAOT, you can instead use "DirectPInvoke" which
| links against specified binary and relies on system
| loader just like C/C++ code would. Then, you can also
| statically link and embed the dependency into your binary
| (if .lib/.a is available) instead which will turn
| pinvokes into direct calls (marshalling if applicable and
| GC frame transition remain, on that read below).
|
| Lastly, it is beneficial to annotate short-lived PInvoke
| calls with [SuppressGCTransition] which avoids some
| deoptimizations and GC frame transition calls around
| interop and makes the calls as cheap as direct calls in C
| + GC poll (a single usually not-taken branch). With this
| the cost of interop effectively evaporates which is one
| of the features that makes .NET as a _relatively_ high-
| level runtime so good at systems programming.
|
| Unmanaged function pointers have similar overhead, and
| identical if you apply [SuppressGCTransition] to them in
| the same way.
|
| * LibraryImport is not needed if pinvoke signature only
| has primitives, structs that satisfy 'unmanaged'
| constraint or raw pointers since no marshalling is
| required for these.
| zigzag312 wrote:
| Saving this as I don't remember seeing such succinct
| explanation of these attributes before :)
| MarkSweep wrote:
| There is a runtime (not C#) feature that has been added that
| is relevant to the article: ref fields in structs. Before
| these, only certain runtime-blessed types like Span<T> could
| contain refs directly.
|
| In case anyone is interested, here is the spec about refs in
| structs and other lifetime features mentioned in the article:
|
| https://github.com/dotnet/csharplang/blob/main/proposals/csh.
| ..
|
| And here is the big list of ways .NET differs from the
| publish ECMA spec. Some of these differences represent new
| runtime features.
|
| https://github.com/dotnet/runtime/blob/main/docs/design/spec.
| ..
| moomin wrote:
| And the latest version is massively relaxing the ref
| restrictions on generics.
| algorithmsRcool wrote:
| Span and ref-like types enable massive changes to the way that
| memory is managed in C#. You can absolutely write almost GC-
| less code. I have been tinkering with a toy no-GC database
| engine in C# based on Direct I/O and some object pooling. I
| have been amazed at how far i can get before resorting to GC
| heap allocations
| DeathArrow wrote:
| Anything that uses classes and interfaces will be memory
| managed by the GC. So instead of using lists, dictionaries,
| IEnumerable, you will have to roll your own.
|
| It would be better if the GC can be turned off with a switch
| and just add a delete operator to manually free memory.
| neonsunset wrote:
| Given that ref structs can now be generic arguments and
| cannot be boxed - you have more ways to enforce that no
| boxing occurs at compile-time. It is true that you have to
| roll your own collections, but even dispatching on
| interfaces by making them generic constraints (which is
| zero-cost) instead of boxing is a good start.
|
| As for delete operator, 'dispose' works well enough. I have
| a toy native vector that I use for all sorts of one-off
| tasks: // A is a shorthand for default
| allocator, a thin wrapper on top of malloc/realloc/free
| // this allows for Zig-style allocator specialization
| using var nums = (NVec<int, A>)[1, 2, 3, 4];
| nums.Add(5); ... // underlying pointer is freed
| at the end of the scope
|
| It is very easy to implement and I assume C and C++
| developers would feel right at home, except with better UX.
|
| This retains full compatibility with the standard library
| through interfaces and being convertible to Span<T>, which
| almost everything accepts nowadays.
|
| System-provided allocators are slower at small allocations
| than GC, but Jemalloc easily fixes that.
| DeathArrow wrote:
| I really mean using existing stuff, without rolling your
| own:
|
| List<int> nums = [1, 2, 3, 4];
|
| //do stuff with nums
|
| Delete(nums);
| neonsunset wrote:
| Okay, I see where you are coming from. This is a common
| ask, but it works against the principles that make
| generational GCs performant. You can't "delete" an object
| from the heap, because dead objects are not deallocated.
| Instead, live objects are preserved and moved to an older
| generation, with memory now occupied by only dead objects
| made available for subsequent allocations immediately.
|
| In addition, objects that hold references to other
| objects internally would need an implementation that
| would allow to traverse and recursively free references
| in a statically understood way. This gets nasty quick
| since a List<T> can hold, let's say, strings, which may
| or may not have other locations referring to them. Memory
| safety goes out of the window for dubious performance
| wins (not even necessarily, since this is where GC has
| better throughput).
|
| I can recommend watching the lectures from Konrad Kokosa
| that go into the detail how .NET's GC works: https://www.
| youtube.com/watch?v=8i1Nv7wGsjk&list=PLpUkQYy-K8...
| DeathArrow wrote:
| > Okay, I see where you are coming from. This is a common
| ask, but it works against the principles that make
| generational GCs performant.
|
| In my comment I already suggested a context where GC can
| be turned off. I said: "It would be better if the GC can
| be turned off with a switch and just add a delete
| operator to manually free memory."
| whizzter wrote:
| And that'd totally break down as soon as some underlying
| class does something you didn't expect. C++ RAII patterns
| and Rust's ownership systems are required for a very good
| reason (that the GC sidesteps but also makes all code
| dependent of), the NVec further up in the thread works
| because it's an explicit abstraction.
| pjmlp wrote:
| Use the stuff from Marshal and OS interop then, there are
| even malloc/free variants.
|
| Also there is C++ for that, if the goal is to use C# as
| C++.
| zigzag312 wrote:
| This looks intriguing. Is there anywhere I could see more
| details about this?
| neonsunset wrote:
| https://github.com/neon-sunset/project-
| anvil/blob/master/Sou...
|
| This really is a PoC. You might get better results by
| using snippets as the inspiration for rolling something
| tailored to your specific use-case.
| zigzag312 wrote:
| Thank you! It'll be fine learning resource.
| ComputerGuru wrote:
| > Given that ref structs can now be generic arguments
|
| I missed this development! That was a big pain working
| with ref structs when they first came out.
| algorithmsRcool wrote:
| Ref-structs can also implement interfaces now too. The C#
| compiler team has been really delivering in this space
| the last few iterations
| algorithmsRcool wrote:
| >Anything that uses classes and interfaces will be memory
| managed by the GC...
|
| Yes and no. Yes, almost all of the standard library
| collection are allocation heavy and it is still the
| dominate pattern in C#, so if you want to avoid the GC you
| need to avoid these and resort to building your own
| primitives based on Memory/Span. Which sucks.
|
| However, you can use interfaces in a no GC world since you
| can constrain those interfaces to be structs or ref-structs
| and the compiler will enforce rules that prevent them from
| being boxed onto the GC heap.
|
| Also of recent note, the JIT can now automagically convert
| simple gc-heap allocations into stack allocations if it can
| trivially prove they don't escape the stack context.
|
| > It would be better if the GC can be turned off with a
| switch and just add a delete operator to manually free
| memory.
|
| It is a little know fact that you can actually swap out the
| GC of the runtime. So you could plug in a null
| implementation that never collects (at your own peril...)
|
| As for a delete operator, you can just roll your own struct
| based allocation framework that uses IDisposable to reclaim
| memory. But then you need to deal with all the traditional
| bugs like use-after-free and double-free and the like.
|
| For me, I think low-gc is the happy medium. Avoid the heap
| in 99% of cases but let the GC keep things air tight
| recursive wrote:
| > constrain those interfaces to be structs or ref-structs
|
| How?
|
| I know of constraints on generic type parameters, but not
| how to do this. A cursory search is unhelpful.
| neonsunset wrote:
| I think the comment just meant using generic constraints
| with structs.
|
| e.g. interface Foo { int
| Calculate(); } static void
| CalculateThing<T>(T impl) where T: Foo {
| var num = impl.Calculate() * 2;
| Console.WriteLine(num); }
|
| Here if you pass a struct that implements 'Foo',
| 'CalculateThing' will be monomorphized and the dispatch
| will be zero-cost, same as in Rust.
|
| You can apply additional constraints like `where T:
| struct` or `allows ref struct`. The last one is a new
| addition which acts like a lifetime restriction that says
| that you are not allowed to box T because it may be a ref
| struct. Ref structs are for all intents and purposes
| regular structs that can hold so-called "managed
| references" aka byrefs, which have syntax 'ref T', which
| is discussed in detail by the article this submission
| links to (ref structs can also hold other ref structs,
| you are not limited in nesting, but you are limited in
| cyclicality).
| algorithmsRcool wrote:
| Yes, this is what I meant
| algorithmsRcool wrote:
| > As for a delete operator, you can just roll your own
| struct based allocation framework that uses IDisposable
| to reclaim memory. But then you need to deal with all the
| traditional bugs like use-after-free and double-free and
| the like.
|
| I forgot that there is built in support for this model
| using the MemoryManager<T> class [0]. A memory manager is
| an abstract class that represents a block of other
| memory, including possibly unmanaged memory. It
| implements IDisposable already so you can just plug into
| this.
|
| The Memory<T> struct can optionally internally point to a
| MemoryManager instance allowing you to plug your perfered
| style of allocation and freeing of memory into parts of
| the framework.
|
| There is a little irony that a MemoryManager<T> is itself
| a class and therefore managed on the gc-heap, but you can
| defeat this by using ObjectPool<T> to recycle those
| instances to keep allocation count steady state and not
| trigger the GC.
|
| I have used this before (in the toy database i mentioned
| earlier) to allocate aligned blocks of unmanaged memory.
|
| [0] https://learn.microsoft.com/en-
| us/dotnet/api/system.buffers....
| yarg wrote:
| > It would be better if the GC can be turned off with a
| switch and just add a delete operator to manually free
| memory.
|
| This breaks the fundamental assumptions built into pretty
| much every piece of software ever written in the language -
| it's a completely inviable option.
|
| Incorporating a borrow checker allows for uncollected code
| to be incorporated without breaking absolutely everything
| else at the same time.
| pjmlp wrote:
| This kind of features is C# catching up to what was already
| possible in languages like Modula-3.
|
| Unfortunately, as usual in computing, we have to do huge
| circles shaped in zig-zag, instead of adopting what was right
| in front of us.
| johnisgood wrote:
| Yeah I feel like we are taking "reinventing the wheel" to a
| whole new level, and with enough time, people forget, same
| with Lisp and Forth (i.e. how they just re--implement stuff
| that were already a thing in those two languages, but perhaps
| under a different name).
| jason_oster wrote:
| As completely off topic as my response will be, I'll at
| least keep to the theme of this thread. I was reminded the
| other day of an article that called out React and Flux for
| reinventing the Windows 1.0 dispatch architecture, and it
| made me laugh: https://www.bitquabit.com/post/the-more-
| things-change/
|
| Also, can't miss the opportunity to bring up Graydon's
| iconic 2010 talk "Technology from the past come to save the
| future from itself". http://venge.net/graydon/talks/
| uticus wrote:
| The biggest difference is the number of people involved and
| the target. C# is built for collaboration by a large number
| of people (of sometimes little experience), for everything
| from Windows GUI to microservice AWS lambdas
| pjmlp wrote:
| Of course, the point is that this all traces back to Java
| being a language originally designed for settop boxes,
| leaving the features of Oberon/Cedar/Modula-3/Eiffel/...
| behind, C# being born out of Sun's lawsuit when J++ was the
| original language for Ext-VOS, WinDev resistance to
| anything not C and C++, Singularity, Midori, Phoenix,
| languages like D, Go, Rust gaining attention, and so on and
| on.
|
| Lots of zig-zags.
|
| I am a firm believer that if languages like Java and C# had
| been like those languages that predated them, most likely C
| and C++ would have been even less relevant in the 2010's,
| and revisions like C++11 wouldn't have been as important as
| they turned out to be.
| uticus wrote:
| Well said, I agree
| algorithmsRcool wrote:
| There is very little new under the sun. It reminds me of the
| wheel of time books, as the wheel turns we forget about the
| learnings of the previous age and reinvent them for
| ourselves. Often worse.
| stonethrowaway wrote:
| C# is Pragmatic [0].
|
| [0] https://news.ycombinator.com/item?id=41761346
| lowbloodsugar wrote:
| >This restriction is central to Rust's safety guarantees, but C#
| doesn't need it. The reason is that Rust has to account for the
| possibility that a reference may be invalidated at any time.
|
| That's not why though. There's lots of reasons for Rusts safety
| model, such as allowing for vastly faster code because aliasing
| can't happen unless both references are read only, in which case
| it doesn't matter. There is lot to Rusts borrow rules that this
| article misses.
|
| It's like the article earlier today that was, essentially, "I
| don't understand Rust and it would be better if it was Haskell".
| fulafel wrote:
| Is there some benchmarking work that has quantified this
| speedup from aliasing guarantees or is this more of a
| sufficiently smart compiler[1] thing?
|
| [1] https://kidneybone.com/c2/wiki/SufficientlySmartCompiler
| DonaldPShimoda wrote:
| The Sufficiently Smart Compiler is a hypothetical argument
| used in advocating for a programming language whose naive
| implementations might tend towards inefficient designs,
| whereas the aliasing argument is not itself hypothetical but
| it may not be backed by data. That's a different thing
| altogether.
|
| Whether the aliasing argument holds water does not affect
| whether it was used as justification for Rust's design.
| vlovich123 wrote:
| Of course: https://github.com/rust-
| lang/rust/issues/54878#issuecomment-...
|
| TLDR: 0-5% faster with noalias optimizations on.
|
| You can always try running some benchmarks by building code
| with -Zmutable-noalias=no.
| fulafel wrote:
| Thanks. If anyone has a link to results handy I'd be
| intersted. In particular a lot changes can be said to be
| 0-5% where the average is very close to 0...
| physicsguy wrote:
| > aliasing can't happen unless both references are read only
|
| Other languages have long had aliasing, Fortran for one. C and
| C++ have the restrict keyword though obviously it's a
| programmer guarantee there and is less safe, since if the user
| of the function does pass the same memory ref offset for e.g.
| the optimisation is not safe.
| orangeboats wrote:
| The restrict keyword is very seldomly used in C programs. You
| could probably remove it and still be able to compile the
| majority of C programs.
|
| (Otherwise, the Rust project wouldn't have encountered all
| the bugs related to aliasing analysis in LLVM.)
| kazinator wrote:
| The ISO C standard uses the restrict qualifier on some
| standard library function declarations in such a way that
| it does nothing.
| physicsguy wrote:
| It's not used much in a sense that it's applied to very few
| functions, but it is well known and widely used in limited
| circumstances. The bugs in LLVM (and GCC) are basically
| because Rust is using aliasing much more widely than it
| ever has been in most C programs.
|
| Take for e.g. this: void add(double *A,
| double *B, double *C, int N) { for(int i = 0; i
| < N; i++) { C[i] = A[i] + B[i];
| } }
|
| You generally wouldn't find many C developers sprinkling
| restrict in on functions like this, since that function
| could be useful to someone using add on two overlapping
| arrays.
|
| On the other hand, someone writing a ODE solver in a
| scientific code might write a function like this, where it
| would never make sense for the memory locations to overlap:
| void RHS(double* restrict x, double* restrict xdot, int N,
| double dt) { for(int i = 0; i < N; i++) {
| xdot[i] = -x[i]/dt; } }
|
| In those sorts of circumstances, it's one of the first
| performance optimisations you might reach for in your C/C++
| toolkit, before starting to look at for e.g. parallelism.
| It's been in every simulation or mathematical code base
| I've worked on in 10+ years at various different academic
| institutions and industry companies.
| vlovich123 wrote:
| I think you're being overly optimistic in assuming that
| is actually the case. Bugs like
| https://bugs.llvm.org/show_bug.cgi?id=39282 make me think
| that even simple examples like you wrote could have had
| problems. Here's a similar bug but for GCC:
| https://github.com/rust-
| lang/rust/issues/54878#issuecomment-.... I fully expect
| MSVC to be full of such bugs.
|
| I'm sure there were probably others.
|
| It's generally true that C/C++ code rarely if ever uses
| restrict & that Rust was the first to actually put any
| real pressure on those code paths and once it was found
| it took over a year to fix and it's incorrect to state
| that the miscompilation was only in code patterns that
| would only exist in Rust.
| physicsguy wrote:
| In the areas I've worked these sorts of cases would have
| been picked up by tests, and especially checking for
| correctness of output between different optimisation
| levels. But I can concede that that's perhaps not the
| standard sort of workflow for many C/C++ developers.
| int_19h wrote:
| You should be able to remove `restrict` in any valid C
| program and still compile it and run it with the same
| result, no? _Adding_ `restrict` can make otherwise valid
| code UB if there 's aliasing, but the reverse shouldn't
| ever appply.
| umanwizard wrote:
| I think OP meant you could remove the "restrict" keyword
| from the _language_ and most programs would still compile
| fine.
| vlovich123 wrote:
| > C and C++ have the restrict keyword
|
| I'd say in name only given that there were numerous aliasing
| bugs in llvm that only became visible when Rust tried to
| leverage it. I suspect similar pitfalls exist in every single
| C/C++ compiler because the rules for restrict are not only
| difficult to understand for use but also difficult to
| implement correctly.
| physicsguy wrote:
| Left a comment on another reply, but as I said there,
| there's a big difference in approach because restrict
| usually only gets sprinkled in on very few functions, but
| it doesn't mean it's not used.
| DonaldPShimoda wrote:
| > Instead of throwing an exception, we've decided that this
| function should always return something, even if it's not in the
| haystack
|
| The right move at this point would be to use an optional type,
| surely...
| vlovich123 wrote:
| > This restriction is central to Rust's safety guarantees, but C#
| doesn't need it. The reason is that Rust has to account for the
| possibility that a reference may be invalidated at any time.
|
| Is this right? I thought Rust's reason for XOR is deeper & is how
| it also guarantees memory safety for multi-threaded code too (&
| not just for reference lifetimes).
| lionkor wrote:
| That's how I understood it, too
| Animats wrote:
| So what are the C# compiler lifetime error messages like? It if
| guesses about lifetimes, the messages have to be good.
| lionkor wrote:
| The article has some examples
| leosarev wrote:
| > Maybe I'm bad at searching for these things, but these changes
| to C# seem to have gone completely under the radar in places
| where you read about memory safety and performance.
|
| The reason is this changes are not aimed on average Joe developer
| writing C# microservices. This changes and whole Span/ref dialect
| of C# are aimed on Dr. Smartass developer writing C# high
| performance libraries. It's advance-level feature.
| yread wrote:
| The article that introduced them talks about mainstay and is
| certainly not trying to hide their impact
|
| https://learn.microsoft.com/en-us/archive/msdn-magazine/2018...
| DeathArrow wrote:
| I use low level C# constructs, mostly for fun. At current job
| we write backend microservices and our business domain doesn't
| need too much low level stuff.
|
| But before span and friends you could always use pointers.
| Spans just make things friendlier.
|
| And C# also has built-in SIMD libraries if you need to do some
| high performance arithmetic stuff.
| bob1029 wrote:
| I don't think Span<T> and ref are particularly sophisticated
| concepts.
|
| Span makes working with large buffers easier for Joe developer,
| if he could be bothered to spend 20 seconds looking at the
| examples in the documentation.
| EVa5I7bHFq9mnYK wrote:
| All this "advance" stuff does is work around the too clever
| memory model and allow to simply allocate data on stack,
| something invented ~60 years ago.
| WorldMaker wrote:
| C# always supported stack allocations and always supported
| things like stack-based pointer operations. It just tagged a
| lot of it as "Unsafe" and/or required the `unsafe` language
| keyword (and concomitant security escalation in the old
| Framework security model where assemblies that used `unsafe`
| code needed additional code certificates to be installed into
| places like the GAC).
|
| The "advanced" stuff _is_ very much about bringing Rust-like
| lifetimes to the language and moving the powers and
| capabilities outside of the `unsafe` keyword world, by making
| it much less unsafe in similar ways to how Rust does lifetime
| /borrow-checking but converted to C#/CLR's classic type
| system. It's adding the "too clever" memory model of Rust to
| the much simpler memory model of a GC. (GCs are a very simple
| memory model invented ~70 years ago.)
| CharlieDigital wrote:
| The absolute best way to stay on top of C# changes is the
| "What's New" docs: https://learn.microsoft.com/en-
| us/dotnet/csharp/whats-new/cs...
|
| Basically gives you a release-by-release highlight reel of
| what's changed and why it's changed.
|
| I glance at it every release cycle to get an idea of what's
| coming up. The even numbered releases are LTS releases while
| the odd numbered releases (like the forthcoming 9) are short
| term. But the language and runtime are fairly stable now after
| the .NET Framework -> .NET Core turbulence and now runtime
| upgrades are mostly just changing a value in a file to select
| your target language and runtime version.
| zero0529 wrote:
| Is there a RSS feed for this? I can't find it unfortunately
| CharlieDigital wrote:
| For that, your best bet is
| https://devblogs.microsoft.com/dotnet/
| snoman wrote:
| There's some YouTubers that regularly cover these if youre
| the type that enjoys watching it instead. Nick Chapsas is one
| I enjoy watching.
| daxfohl wrote:
| So why all the interest in rust, comparatively?
|
| My assumption is since there is a GC, and it is not native
| code, there are too many use cases where it can't apply, but
| rust can. Once there is a way to have it compete with rust in
| every use case rust can be used, maybe there will be more talk.
| neonsunset wrote:
| What does .NET compile IL to? :)
| stackghost wrote:
| Garbage collection doesn't imply interpreted. Common Lisp has
| had GC'ed compiled code for decades.
| umanwizard wrote:
| Or Go, for a more mainstream modern example.
| daxfohl wrote:
| Yeah but C# is. Or, compiled to IL and JITted. Unless there
| is an AOT thing now that can truly compete with c and rust.
| Been out of the ecosystem for a while.
| zigzag312 wrote:
| NativeAOT
|
| https://learn.microsoft.com/en-
| us/dotnet/core/deploying/nati...
| DeathArrow wrote:
| >How is it possible that I can write efficient and provably-safe
| code in C# without a degree in type theory?
|
| Because Anders Hejlsberg is one of the greatest language
| architects and the C# team are continuing that tradition.
|
| The only grudge I have against them is they promised us
| discriminated unions since forever and they are still discussing
| how to implement it. I think that is the greatest feature C# is
| missing.
|
| For the rest C# is mostly perfect. It has a good blend of
| functional and OOP, you can do both low level and high level
| code. You can target both the VM or the bare hardware. You can
| write all types of code beside system programming (due to the
| garbage collector). But you can do web backend, web front-end,
| services, desktop, mobile apps, microcontroller stuff, games and
| all else. It has very good libraries and frameworks for whatever
| you need. The experience with Visual Studio is stellar.
|
| And the community is great. And for most domains there is
| generally only one library or framework everybody uses so you not
| only don't have to ask what to use for a new feature or project,
| but you also find very good examples and help if you need.
|
| It feels like a better, more strait trough version of Java, less
| verbose and less boiler plate-y. So that's why .NET didn't need
| its own Kotlin.
|
| Sure, it can't meet the speed of Rust or C# for some tasks
| because of the garbage collector. But provided you AOT compule,
| disable the garbage collector and do manual memory management, it
| should.
| jabwd wrote:
| Except that the GC makes it exactly not viable for games and
| its one of the biggest problems Unity devs run into. I agree
| it's a great language, but its not a do it all.
| neonsunset wrote:
| Unity (and its GC) is not representative of the performance
| you get with CoreCLR.
|
| The article discusses ref lifetime analysis that does have
| relationship with GC, but it does not force you into using
| one. Byrefs are very special - they can hold references to
| stack, to GC-owned memory and to unmanaged memory. You can
| get a pointer to device mapped memory and wrap it with a
| Span<T> and it will "just work".
| mafuy wrote:
| Well, when I worked in Unity I used to compile C# code with
| the LLVM backend. It was as fast as C++ code would be. So
| Unity is perhaps an example in favor of C#.
| SeasonalEnnui wrote:
| I could argue the opposite - GC makes it more viable for
| games. "GC is bad" misses too much nuance. It goes like this:
| developer very quickly and productively gets minimum viable
| game going using naive C# code. Management and investors are
| happy with speed of progress. Developers see frame rate
| stutters, they learn about hot path profiling, gen0/1/2/3 GC
| & how to keep GC extremely fast, stackalloc, array pooling,
| Span<T>, native alloc; progressively enhancing quickly until
| there are no problems. These advanced concepts are quick and
| low risk to use, and in the case of many of the advanced
| concepts; what you would be doing in other languages anyway.
| DeathArrow wrote:
| The only reason we might see FPS drop in games, is not
| because C# and its GC. It's mostly because the poor usage
| of the graphics pipeline and the lack of optimization. As a
| former game developer I had to do a lot of optimization so
| our games run nicely on mobile phones with modest hardware.
|
| C# it's plenty fast for game programming.
| SeasonalEnnui wrote:
| A good datapoint, thanks. Extending my original point -
| C# got _really_ good in the last 5 years with regards to
| performance & low-level features. There might be an
| entrenched opinion problem to overcome here.
| mycocola wrote:
| That entirely depends on the game. Recent example is Risk
| of Rain 2, which had frequent hitches caused by the C#
| garbage collector. Someone made a mod to fix this by
| delaying the garbage collection until the next load-
| screen -- in other words, controlled memory leakage.
|
| The developers of Risk of Rain 2 were undoubtedly aware
| of the hitches, but it interfered with their vision of
| the game, and affected users were left with a degraded
| experience.
|
| It's worth mentioning that when game developers scope of
| the features of their game, available tech informs the
| feature-set. Faster languages thus enable a wider
| feature-set.
| n4r9 wrote:
| Apparently that was released in 2019? Both C# and dotnet
| have had multiple major releases since then, with
| significant performance improvements.
| Rohansi wrote:
| The GC that Unity is using is extremely bad by today's
| standards. C# everywhere else has a significantly better
| GC.
|
| In this game's case though they possibly didn't do much
| optimization to reduce GC by pooling, etc. Unity has very
| good profiling tools to track down allocations built in
| so they could have easily found significant sources of GC
| allocations and reduced them. I work on one of the larger
| Unity games and we always profile and try to pool
| everything to reduce GC hitches.
| munificent wrote:
| _> It 's worth mentioning that when game developers scope
| of the features of their game, available tech informs the
| feature-set. Faster languages thus enable a wider
| feature-set._
|
| This is true, but developer productivity also informs the
| feature set.
|
| A game could support all possible features if written
| carefully in bare metal C. But it would take two decades
| to finish and the company would go out of business.
|
| Game developers are always navigating the complex
| boundary around "How quickly can I ship the features I
| want with acceptable performance?"
|
| Given that hardware is getting faster and human brains
| are not, I expect that over time higher level languages
| become a better fit for games. I think C# (and other
| statically typed GC languages) are a good balance right
| now between good enough runtime performance and better
| developer velocity than C++.
| Const-me wrote:
| > frequent hitches caused by the C# garbage collector
|
| They probably create too much garbage. It's equally easy
| to slow down C++ code with too many malloc/free functions
| called by the standard library collections and smart
| pointers.
|
| The solution is the same for both languages: allocate
| memory in large blocks, implement object pools and/or
| arena allocators on top of these blocks.
|
| Neither C++ nor C# standard libraries have much support
| for that design pattern. In both languages, it's
| something programmers have to implement themselves. I did
| things like that multiple time in both languages. I found
| that, when necessary, it's not terribly hard to implement
| that in either C++ or C#.
| smaudet wrote:
| > In both languages, it's something programmers have to
| implement themselves.
|
| I think this is where the difference between these
| languages and rust shines - Rust seems to make these
| things explicit, C++/C# hides behind compiler warnings.
|
| Some things you can't do as a result in Rust, but really
| if the rust community cares it could port those features
| (make an always stack type type, e.g.).
|
| Code base velocity is important to consider in addition
| to dev velocity, if the code needs to be significantly
| altered to support a concept it swept under the rug e.g.
| object pools/memory arenas, then that feature is less
| likely to be used and harder to implement later on.
|
| As you say, it's not hard to do or a difficult concept to
| grasp, once a dev knows about them, but making things
| explicit is why we use strongly typed languages in the
| first place...
| bluGill wrote:
| Anybody writing a game should be writing in a game engine.
| There are too many things you want in a game that just come
| "free" from an engine that you will spend years writing by
| hand.
|
| GC can work or not when writing a game engine. However
| everybody who writes a significant graphical game engine in
| a GC language learns how to fight the garbage collector -
| at the very least delaying GC until between frames. Often
| they treat the game like safety critical: preallocate all
| buffers so that there is no garbage in the first place (or
| perhaps minimal garbage). Without garbage collection might
| technically use more CPU cycles, but in general they are
| spread out more over time and so more consistent.
| greener_grass wrote:
| > not viable for games
|
| > Unity devs run into
|
| So it's viable but not perfect
| DeathArrow wrote:
| > one of the biggest problems Unity devs run into
|
| Unity used Mono. Which wasn't the best C# implementation,
| performance wise. After Mono changed its license, instead of
| paying for the license, Unity chose to implement their
| infamous IL2CPP, which wasn't better.
|
| Now they want to use CoreCLR which is miles better than both
| Mono and IL2CPP.
| Paradigma11 wrote:
| Doesnt Unity use its own GC or transpiles to C++? Unity on
| .Net core is more than a year away, no?
| pjmlp wrote:
| It uses the prehistoric Mono GC. Additionally it transpiles
| IL to C++ due to many targets like consoles, and iDevices,
| not allowing for a JIT.
|
| They also have a C# subset called Burst, which could have
| been avoided if they were using .NET Core.
| bob1029 wrote:
| GC in modern .NET runtime is quite fast. You can get very low
| latency collections in the normal workstation GC mode.
|
| Also, if you invoke GC intentionally at convenient timing
| boundaries (I.e., after each frame), you may observe that the
| maximum delay is more controllable. Letting the runtime pick
| when to do GC is what usually burns people. Don't let the
| garbage pile up across 1000 frames. Take it out every chance
| you get.
| Traubenfuchs wrote:
| > if you invoke GC intentionally at convenient timing
| boundaries (I.e., after each frame),
|
| Manually invoking GC many times per second is a viable
| approach?
| bob1029 wrote:
| It has worked well in my prototypes. There is a reason a
| GC.Collect method is exposed for use.
| smaudet wrote:
| At least for this instance you have a good idea which
| objects are "ripe" for collection. There should be some
| way to specify "collect these, my infra objects don't
| need to be".
| munificent wrote:
| It can be, yes.
|
| You're basically trading off worse throughput for better
| latency.
|
| If you forcibly run the GC every frame, it's going to
| burn cycles repeatedly analyzing the same still-alive
| objects over and over again. So the overall performance
| will suffer.
|
| But it means that you don't have a big pile of garbage
| accumulating across many frames that will eventually
| cause a large pause when the GC runs and has to visit all
| of it.
|
| For interactive software like games, it is often the
| right idea to sacrifice maximum overall efficiency for
| more predictable stable latency.
| neonsunset wrote:
| This might be more problematic under CoreCLR than under
| Unity. Prematurely invoking GC will cause objects that
| are more likely to die in Gen 0 to be promoted to Gen 1,
| accumulate there and then die there. This will cause
| unnecessary inter-generational traffic and will extend
| object lifetimes longer than strictly necessary. Because
| live object count is the main factor that affects pause
| duration, this may be undesirable.
|
| It might be more useful to use OSU! approach as a
| reference: https://github.com/dotnet/runtime/issues/96213
| #issuecomment-...
|
| OSU! represents an extreme case where the main game loop
| runs at 1000hz, so for much more realistic ~120hz you
| have plenty of options.
| smaudet wrote:
| If you could even just pass an array of objects to be
| collected or something, this would so much easier.
|
| Magic, code or otherwise, sucks when the
| spell/library/runtime has different expectations than
| your own.
|
| You expect levitation to apply to people, but the runtime
| only levitates carbon based life forms. You end up
| levitating people without their affects (weapons/armor),
| to the embarrassment of everyone.
|
| There should be no magic, everything should be
| parameterized, the GC is a dangerous call, but it should
| be exposed as well (and lots of dire warnings issued to
| those using it).
| munificent wrote:
| _> If you could even just pass an array of objects to be
| collected or something_
|
| If you have a bunch of objects in an array that you have
| a reference to such that you can pass it, then, by
| definition, those objects are not garbage, since they're
| still accessible to the program.
| smaudet wrote:
| Yes. Use a WriteOnlyArray or whatever, Semantics aside
| though...
|
| There should be some middle ground between RAII and
| invoking Dispose/delete and full blown automatic GC.
| pjmlp wrote:
| Except that is a matter of developer skill, and Unity using
| Mono with its lame GC implementation, as proven by CAPCOM's
| custom .NET Core fork based engine used for Devil May Cry on
| the PlayStation 5.
| smaudet wrote:
| We can all agree Unity is terrible.
|
| Would be nice to hear about a Rust Game engine, though.
| pjmlp wrote:
| Check Bevy.
| lmm wrote:
| Many of the top games in recent years have used it, so you've
| got a funny definition of "not viable".
| johnisgood wrote:
| Or roll their own, so they used GC in one way or another.
| rafaelmn wrote:
| C# has much better primitives for controlling memory layout
| than Java (structs, reified generics).
|
| BUT it's definitely not a language designed for no-gc so
| there are footguns everywhere - that's why Rider ships
| special static analysis tools that will warn you about this.
| So you can keep GC out of your critical paths, but it won't
| be pretty at that point. But better than Java :D
| neonsunset wrote:
| > but it won't be pretty at that point
|
| Possibly prettier than C and C++ still. Every time I write
| something and think "this could use C" and then I use C and
| then I remember why I was using C# for low-level
| implementation in the first place.
|
| It's not as sophisticated and good of a choice as Rust, but
| it also offers "simpler" experience, and in my highly
| biased opinion pointers-based code with struct abstractions
| in C# are easier to reason about and compose than more
| rudimentary C way of doing it, and less error-prone and
| difficult to work with than C++. And building final product
| takes way less time because the tooling is so much
| friendlier.
| zigzag312 wrote:
| Games would need alternative GC optimized for low latency
| instead of maximum throughput.
|
| AFAIK it has been possible to replace the GC with alternative
| implementation for the past few years, but no one has made
| one yet.
|
| EDIT: Some experimental alternative GC implementations:
|
| https://github.com/kkokosa/UpsilonGC
|
| https://www.codeproject.com/Articles/5372791/Implementing-a-.
| ..
| lifthrasiir wrote:
| At least for Unity, the actual problem lies in IL2CPP and not
| C#. I have professionally used C# in real-time game servers
| and GC was never a big issue. (We did use C++ in the lower
| layer but only for the availability of Boost.Asio, database
| connectors and scripting engines.)
| Rohansi wrote:
| Unity lets you use either IL2cPP (AOT) or Mono (JIT).
| Either way it will use Boehm GC which is a lot worse than
| the .NET GC. If your game servers weren't using Unity then
| they are using a better GC.
| lifthrasiir wrote:
| Yeah, we rolled our own server framework in .NET mainly
| because we were doing MMOs and there were no off-the-
| shelf frameworks (including Unity's) explicitly designed
| for that. In fact, I believe this is still mostly true
| today.
| alkonaut wrote:
| It's hard to use C# without creating garbage. But it's not
| impossible. Usually you'd just create some arenas for your
| important stuff, and avoid allocating a lot of transient
| objects such as enumerators etc. So long as you can generate
| 0 bytes of allocation each frame, you won't need a GC no
| matter how many frames you render. The question is only this:
| does it become so convoluted that you could just as well have
| used C++?
| jayd16 wrote:
| Enumerators are usually value types as long as you use the
| concrete type. Using the interface will box it. You can
| work around this by simply using List<T> as the type
| instead of the IEnnumerable.
|
| You have to jump through some hoops but it's really not
| that convoluted and miles easier than good C++.
| alkonaut wrote:
| The problem with it is that you don't know. The
| fundamental language construct "foreach" is one that may
| or may not allocate and it's hard for you as a developer
| to be sure. Many other low level things do this or at
| least used to (events/boxing/params arrays, ...).
|
| I wish there was an attribute in C# that was
| "[MustNotAllocate]" which files the compilation on
| _known_ allocations such as these. It 's otherwise very
| easy to accidentally introduce some tiny allocation into
| a hot loop, and it only manifests as a tiny pause after
| 20 minutes of runtime.
| neonsunset wrote:
| Most often you do know whether an API allocates. It is
| always possible to microbenchmark it with
| [MemoryDiagnoser] or profile it with VS or Rider. I
| absolutely love Rider's dynamic program analysis that
| just runs alongside me running an application with F5,
| ideally in release, and then I can go through every
| single allocation site and decide what to do.
|
| Even when allocations happen, .NET is _much_ more
| tolerant to allocation traffic than, for example, Go. You
| can absolutely live with a few allocations here and
| there. If all you have are small transient allocations -
| it means that live object count will be very low, and all
| such allocations will die in Gen 0. In scenarios like
| these, it is uncommon to see _infrequent_ sub-500us GC
| pauses.
|
| Last but not least, .NET is continuously being improved -
| pretty much all standard library methods already allocate
| only what's necessary (which can mean nothing at all),
| and with each release everything that has room for
| optimization gets optimized further. .NET 9 comes with
| object stack allocation / escape analysis enabled by
| default, and .NET 10 will improve this further. Even
| without this, LINQ for example is well-behaved and can be
| used far more liberally than in the past.
|
| It might sound surprising to many here but among all GC-
| based platforms, .NET gives you the most tools to manage
| the memory and control allocations. There is a learning
| curve to this, but you will find yourself fighting them
| much more rarely in performance-critical code than in
| alternatives.
| ygra wrote:
| While this would be nice for certain applications, I'm
| not sure it's really needed in general. Most people
| writing C# don't have to know about these things, simply
| because it doesn't matter in many applications. If you're
| writing performance-critical C#, you're already on a
| weird language subset and know you way around these
| issues. Plus, allocations in hot loops stand out very
| prominently in a profiler.
|
| That being said, .NET includes lots of performance-
| focused analyzers, directing you to faster and less-
| allocatey equivalents. There surely also is one on NuGet
| that could flag foreach over a class-based enumerator (or
| LINQ usage on a collection that can be foreach-ed
| allocation-free). If not, it's very easy to write and you
| get compiler and IDE warnings about the things you care
| about.
|
| At work we use C# a lot and adding custom analyzers
| ensuring code patterns we prefer or require has been one
| of the best things we did this year, as everyone on the
| team requires a bit less institutional knowledge and just
| gets warnings when they do something wrong, perhaps even
| with a code fix to automatically fix the issue.
| jayd16 wrote:
| If you know what types you're using, you do know. If you
| don't know what you're calling, that's a pretty high bar
| that I'm not sure C++ clears.
| alkonaut wrote:
| If you are calling SomeType.SomeMethod(a, b, c) then you
| don't know what combintions of a, b, c could allocate
| unless you can peek into it or try every combination of
| a, b and c. So it's hard to know in the general case even
| with profiling and testing.
| Rohansi wrote:
| Unity has literally the worst implementation of C# out there
| right now. Not only is it running Mono instead of .NET (Core)
| but it's also not even using Mono's generational GC (SGen).
| They have been working on switching from Mono to .NET for
| years now because Mono isn't being updated to support newer
| C# versions but it will also be a significant performance
| boost, according to one of the Unity developers in this area
| [1].
|
| IL2CPP, Unity's C# to C++ compiler, does not help for any of
| this. It just allows Unity to support platforms where JIT is
| not allowed or possible. The GC is the same if using Mono or
| IL2CPP. The performance of code is also roughly identical to
| Mono on average, which may be surprising, but if you inspect
| the generated code you'll see why [2].
|
| [1] https://xoofx.github.io/blog/2018/04/06/porting-unity-to-
| cor... [2] https://www.jacksondunstan.com/articles/4702 (many
| good articles about IL2CPP on this site)
| para_parolu wrote:
| I believe Unity switched to net core last year
| Rohansi wrote:
| They did not - it is still a work in progress with no
| announced target release date. They also have no current
| plans to upgrade the GC being used by IL2CPP (their C#
| AOT compiler).
|
| https://discussions.unity.com/t/coreclr-and-net-
| modernizatio...
| jayd16 wrote:
| The two biggest engines, Unreal and Unity, use a GC. Unity
| itself uses C#. C# is viable for games but you do need to be
| aware of the garbage you make.
|
| It's really not that hard to structure a game that pre-
| allocates and keeps per frame allocs at zero.
| MaxGripe wrote:
| What do you think about F#, then? It already covers everything
| you mentioned and it has discriminated unions.
| rafaelmn wrote:
| Basically C# is like using a Mac and F# is "I use Arch btw."
| johnisgood wrote:
| Just to comment on the meme: it used to be Gentoo instead
| of Arch in my good old days. :P
| MaxGripe wrote:
| However amusing, this comparison doesn't seem accurate to
| me. F# may appear more challenging only to someone who is
| already accustomed to OO programming languages. For people
| just starting to code, without pre-existing habits,
| learning F# could be much easier than learning C#
| roetlich wrote:
| But knowing to effectively programming F# requires you to
| understand OOP and the functional abstractions. Most
| libraries in .Net still target C#, so understanding C#
| syntax and mentally translating it to F# is often
| required. If your application doesn't require many
| outside dependencies that might be different, but for
| most projects F# will require a lot more learning.
| miloandmilk wrote:
| This 1000 times.
|
| I have been learning F# for a while now, and while the
| functional side that is pushed heavily is a joy to use,
| anything that touches the 'outside world' is going to
| have way more resources for C# as far as libraries,
| official documentation, general information including
| tutorials etc. You will need to understand and work with
| those.
|
| So you really do need to understand C# syntax and
| semantics. Additionally there are a few concepts that
| seem the same in each language but have different
| implementations and are not compatible (async vs tasks,
| records) so there is additional stuff to know about when
| mentally translating between C# and F#.
|
| I really want to love F# but keep banging my head against
| the wall. Elixir while not being typed yet and not being
| as general purpose at least allows me to be productive
| with it's outstanding documentation, abundance of
| tutorials and books on both the core language and domain
| specific applications. It is also very easy to mentally
| translate erlang to elixir and vice versa in the very few
| occasions needed.
| roetlich wrote:
| > I really want to love F# but keep banging my head
| against the wall.
|
| Yeah. What's your opinion on Gleam?
| miloandmilk wrote:
| Gleam from a language perspective seems really nice - but
| it's in it's ramp up stage, I will go through the Gleam
| Exrercism track and keep an eye on it. It would be great
| if it became the general purpose typed pragmatic
| functional language with a large ecosystem I am after!
| Decabytes wrote:
| F# provides escape hatches to OOP since it is closely
| tied to C#.
| rafaelmn wrote:
| It's more of a hassle to get working vs. having
| everything nicely polished and first party support.
| ZeroClickOk wrote:
| No partial classes is painful. I know that we have some
| alternatives, but migrating from C# is a doom without it.
| DeathArrow wrote:
| I absolutely love F#! Two things though: adoption is low so
| you kind of can't use it professionally and most libraries
| are written in C#, so you kind of use them in a non idiomatic
| way.
| zigzag312 wrote:
| > The only grudge I have against them is they promised us
| discriminated unions since forever and they are still
| discussing how to implement it. I think that is the greatest
| feature C# is missing.
|
| To ease the wait you could try Dunet (discriminated union
| source generator).
|
| https://github.com/domn1995/dunet
| CharlieDigital wrote:
| There's also OneOf (https://github.com/mcintyre321/OneOf) as
| well which feels more ergonomic to me (having used both).
|
| Practical example in a short write up here:
| https://chrlschn.dev/blog/2024/07/csharp-discriminated-
| union...
| zigzag312 wrote:
| Isn't OneOf more like a type union, and not a
| tagged/discriminated union?
| whizzter wrote:
| I'm hardly missing discriminated unions (sure exhaustive
| checking would be nice) anymore since the introduction of
| switch expressions that in combination with records handles
| most practical cases.
| CharlieDigital wrote:
| I find DU's particularly useful with
| `System.Threading.Channels` because it lets the channel
| handle multiple types of results in one stream.
| GTP wrote:
| How does C# fares in terms of portability these days? I checked
| years ago, and at the time, for non-Windows OSes you had to use
| Mono. But whether your application was going to work or not
| also depended on which graphic libraries you were using, e.g.
| WinForms wasn't going to work on Mono. At the time, C# was
| presented to me as a better Java, but to me it seemed that Java
| had true cross-platform compatibility while C# was going to
| work nicely only on Windows, unless you had some proper
| planning of checking beforehand which libraries were working
| with Mono.
| Rizu wrote:
| you should checkout AvalonuiaUI[0] or unoPlatform[1] if
| wanting to target web/mobile/window/linux/macOS
|
| if building for the web online, asp.net core runs on Linux
| servers as well as windows
|
| and there's MAUI [2] ( not a fan of this), you are better-off
| with with the others.
|
| in summary c# and .NET is cross-platform, third party
| developers build better frameworks and tools for other
| platform while Microsoft prefers to develop for Microsoft
| ecosystem, if you get
|
| [0] https://avaloniaui.net/ [1] https://platform.uno/ [2]
| https://learn.microsoft.com/en-us/dotnet/maui/what-is-
| maui?v...
| smaudet wrote:
| > and there's MAUI [2] ( not a fan of this), you are
| better-off with with the others.
|
| I will say MS has been obsessed with trying to take a slice
| of the mobile pie.
|
| However their Xamarin/WPF stuff left so much to be desired
| and was such a Jenga Tower that I totally get the community
| direction to go with a framework you have ostensibly have
| more control over vs learning that certain WPF elements are
| causes of e.g. memory leaks...
| CharlieDigital wrote:
| Very good.
|
| I work at one of the few startups that uses C# and .NET.
|
| Dev machines are all M1/M3 MacBook Pros and we deploy to a
| mix of x64 and Arm64 instances on GCP and AWS.
|
| I use VS Code on macOS while the rest of the team prefers
| Rider.
|
| Zero friction for backend work and certainly more pleasant
| than Node. (We still use Node and JS for all front-end work).
| lwansbrough wrote:
| .NET is totally cross platform these days. Our company
| develops locally on Windows and deploys to Linux. I'm the
| only team member on Mac and it works flawlessly.
| bluGill wrote:
| If you only care about linux on x86-64 or some ARM it is
| cross platform. Getting .net on FreeBSD is possible, but it
| isn't supported at all. QNX from what I can tell seems like
| it should be possible but a quick search didn't find anyone
| who succored (a few asked). My company has an in house non-
| posix OS useful for some embedded things, forget about it.
| There are a lot of CPUs out there that it won't work on.
|
| .NET has some small cross platform abilities, but calling
| it totally cross platform is wrong.
| lifthrasiir wrote:
| That's still pretty much cross-platform for all practical
| purposes, as it supports far more platforms than most
| softwares anyway. After all cross-platform only means
| that it runs on multiple platforms, not on all possible
| or even technically feasible platforms. Being cross-
| platform usually means a much easier porting but that
| porting still has to be done somehow.
| smaudet wrote:
| > for all practical purposes
|
| In fairness this ignores a lot of embedded work.
|
| Java gets to cheat here a bit because they have some
| custom embedded stuff, but they are also not actually
| running on all CPUs.
| lifthrasiir wrote:
| Embedded stuffs require much more than mere cross-
| platform anyway ;-)
| neonsunset wrote:
| "Vanilla" .NET runs on
|
| Operating Systems: Linux, macOS, Windows, FreeBSD, iOS,
| Android, Browser
|
| Architectures: x86, x86_64, ARMv6, ARMv7, ARMv8/ARM64,
| s390x, WASM
|
| Notes:
|
| Mono as referred here means
| https://github.com/dotnet/runtime/tree/main/src/mono
| which is an actively maintained runtime flavor, alongside
| CoreCLR.
|
| - Application development targets on iOS and Android use
| Mono. Android can be targeted as linux-bionic with
| regular CoreCLR, but it's pretty niche. iOS has
| experimental NativeAOT support but nothing set in stone
| yet, there are similar plans for Android too.
|
| - ARMv6 requires building runtime with Mono target.
| Bulding runtime is actually quite easy compared to other
| projects of similar size. There are community-published
| docker images for .NET 7 but I haven't seen any for .NET
| 8.
|
| - WASM also uses Mono for the time being. There is a
| NativeAOT-LLVM experiment which promises significant
| bundle size and performance improvements
|
| - For all the FreeBSD slander, .NET does a decent job at
| supporting it - it is listed in all sorts of OS enums,
| dotnet/runtime actively accepts patches to improve its
| support and there are contributions and considerations to
| ensure it does not break. It is present in
| https://www.freshports.org/lang/dotnet
|
| At the end of the day, I can run .NET on my router with
| OpenWRT or Raspberry Pi4 and all the laptops and
| desktops. This is already quite a good level given it's
| completely self-contained platform. It takes a lot of
| engineering effort to support everything.
| SirMaster wrote:
| I mean since Mono it has completely changed. They are about
| to release .NET 9 which is the 8th version (there was no v4
| to reduce confusion with the legacy .NET Framework) since
| being cross-platform.
|
| Mono was a third party glorified hack to get C# to work on
| other OS. .NET has been natively cross platform with an
| entirely new compiler and framework since mid 2016.
| GTP wrote:
| > Mono was a third party glorified hack to get C# to work
| on other OS.
|
| Indeed, this is what I didn't like back then. Java has
| official support for other OSes, which C# was lacking at
| the time. Good to hear that things changed now.
| WorldMaker wrote:
| Back in the day Mono had surprisingly good WinForms support
| on Gtk. It was never going to win awards for pretty and could
| never hit true compatibility with P/Invoke calls to esoteric
| Win32 APIs, but it was good enough to run any simple WinForms
| app you wanted to write for it and ran some of the "popular"
| ones just fine. (That old Mono WinForms support was recently
| donated to Wine, which seems like a good home for it.)
|
| .NET has moved to being directly cross-platform today and is
| great at server/console app cross-platform now, but its
| support for cross-platform UI is still relatively nascent.
| The official effort is called MAUI, has mostly but not
| exclusively focused on mobile, and it is being developed in
| the open (as open source does) and leaves a lot to be
| desired, including by its relatively slow pace compared to
| how fast the server/console app cross-platform stuff moves.
| The Linux desktop support, specifically, seems constantly in
| need of open source contributors that it can't find.
|
| You'll see a bunch of mentions of third-party options
| Avalonia and Uno Platform doing very well in that space,
| though, so there is interesting competition, at least.
| dachris wrote:
| If you're doing ASP.NET Core webdev, it's seamless. Runs in
| Linux docker containers. Developers in my team have either
| Windows (Visual Studio) or Linux or Mac (Rider) machines.
| giancarlostoro wrote:
| Weird, I opened a binary I built years ago on Windows in
| Mono, and it was Winforms and rendered correctly, I think you
| mean WPF and the later GUI techs. Winforms renders nicely on
| Mono for a little while now I think?
|
| There's a lot of options, but also the latest of .NET (not
| Framework) just runs natively on Linux, Mac and Windows, and
| there's a few open source UI libraries as mentioned by others
| like Avalonia that allow your UI to run on any OS.
| GTP wrote:
| The issue at the time was having a WinForms application to
| run also on macOS, and IIRC at the time WinForms wasn't
| supported outside of Windows. Maybe Mono on Windows is
| still different from Mono on macOS. Anyway, the situation
| seem to be much better now. I'm not going to invest time
| into C# at the moment, since I'm in the Java ecosystem and
| Im currently taking some time to practice with Kotlin. But
| it's good to know that now C# is an option as well.
| giancarlostoro wrote:
| Forgot to include my OS: I ran a .NET (Framework? I
| think) .exe I built on Windows in 2020, on Linux with
| Mono and it worked and looked (aside from thematic
| differences) like I remembered it looking in 2024.
| factormeta wrote:
| >The experience with Visual Studio is stellar.
|
| I assume you mean just the Windows Visual Studio? The Mac
| version is not exactly on par with the Windows. Yeah C# is
| great, but one would need Window's version of VS (NOT VS Code)
| to take full advantage of C#. For me that is a deal breaker,
| when the DX of a language is tight to a proprietary sourced IDE
| by MS.
| moomin wrote:
| These days, JetBrains have stepped into the gap with Rider.
| Rider isn't perfect, but there's definitely people who prefer
| it to Visual Studio.
| dachris wrote:
| Rider definitely can hold a candle to Visual Studio. In my
| dev bubble there's about a 50/50 split for C# devs (mostly
| .NET Core) using VS vs Rider
| alkonaut wrote:
| Mac visual studio isn't visual studio, it's something else
| that they stuck the label visual studio on. They are about as
| related as java and javascript (which are famously, related
| as car is to carpet)
| metaltyphoon wrote:
| VS for mac was sunset a while ago. You either use VSC or
| Rider( which is now free for non commercial use )
| contextfree wrote:
| IIRC the last couple of releases had some new/overhauled
| features they said were built for both from the same code,
| so they seemed to be starting down the path of slowly
| converging them, before they changed their minds and
| discontinued the Mac version I guess.
| uticus wrote:
| Incidentally JetBrains Rider (competitor IDE) announced as
| free today for non-commercial, if you'd like to try it out:
|
| https://blog.jetbrains.com/blog/2024/10/24/webstorm-and-
| ride...
|
| [edit: I'll note I've used successfully both Win and Linux]
| alkonaut wrote:
| The DU stuff is enormous once you consider all the corners it
| touches. Especially with refinements. E.g. in code like
|
| if (s is string or s is int) { // what's the type of s here? is
| it "string | int" ? }
|
| And not to mention that the BCL should probably get new
| overloads using DU's for some APIs. But there is at least a
| work in progress now, after years of nothing.
| colejohnson66 wrote:
| One of the claimed benefits of .NET Core was that they could
| improve the runtime at a much faster pace than .NET Framework
| did, especially if that meant adding new features or even IL
| opcodes. And they've done this before, with a big one (IMO)
| being ref fields in ref structs. Lately, when it comes to
| developing C#, the language design team has frustratingly
| been trying to shoehorn _everything_ into the compiler
| instead of modifying the runtime. Then they say the runtime
| should be modified to pattern-match what they output. If DUs
| are to be implemented fully in C#, niches would probably be
| impossible. This means Optional <T>, when T is a class, would
| take two words.
| pif wrote:
| > For the rest C# is mostly perfect.
|
| No, it isn't. The power of C++ templates is still
| astronomically far from C# generics.
| roetlich wrote:
| Yes, another plus point of C#.
|
| Sorry for the snark, but I do think C# compile are just
| barely acceptable for me, so I'm happy they aren't adding
| more heavy compile time features.
| lifthrasiir wrote:
| That power is usually considered too reckless to retain and
| simultaneously too cumbersome to actually use, partly because
| it was never planned in the first place.
| galangalalgol wrote:
| C++ concepts restrain that recklessness, and people hate
| them for it. Rust will get most of that power when they
| finally stabilize const generic expressions. I like c# but
| like the article says, it isn't really borrow checking, so
| you don't get fearless concurrency. If I want to do coarse
| grained multi threading (not just a parallel for loop or
| iterator to speed up math) I only want to use rust now.
| Once I stopped having to think around thread safety issues
| and data consistency, I didn't want to go back. But for
| something single threaded c# or go are great and
| performant.
| jayd16 wrote:
| What about the built in Roslyn source generator stuff? Is
| that sufficiently abusable?
|
| I'm not a templates/macro guy so I'm curious what's missing.
| nick_ wrote:
| There are pros and cons. C++ templates can't be reified at
| run-time like C# generics can.
| int_19h wrote:
| FWIW for C# this also requires having a JIT (to handle
| generic virtual methods).
| neonsunset wrote:
| GVM dispatch is notoriously slow(-ish), yeah. But it does
| not require JIT. Otherwise it wouldn't work with
| NativeAOT :) (the latter can also auto-seal methods and
| unconditionally devirtualize few-implementation members
| which does a good job, guarded devirtualization with JIT
| does this even better however)
| int_19h wrote:
| I remember when this feature was specifically not
| available with NativeAOT.
|
| It's good that it is now, but how can it be implemented
| in a way that has truly separate instantiations of
| generics at runtime, when calls cross assembly
| boundaries? There's no single good place to generate a
| specialization when virtual method body is in one
| assembly while the type parameter passed to it is a type
| in another assembly.
| neonsunset wrote:
| > how can it be implemented in a way that has truly
| separate instantiations of generics at runtime, when
| calls cross assembly boundaries
|
| There are no assembly boundaries under NativeAOT :)
|
| Even with JIT compilation - the main concern, and what
| requires special handling, are collectible assemblies. In
| either case it just JITs the implementation. The cost
| comes from the lookup - you have to look up a virtual
| member implementation and then specific generic
| instantiation of it, which is what makes it more
| expensive. NativeAOT has the definitive knowledge of all
| generic instantiations that exist, since it must compile
| all code and the final binary does not have JIT.
| DeathArrow wrote:
| I don't miss that power. I remember having to modify some
| template heavy code and it brought me to the verge of
| madness. :)
| pif wrote:
| > For the rest C# is mostly perfect.
|
| No! It misses "typedef", both at module API level and within
| generics.
| algorithmsRcool wrote:
| > Because Anders Hejlsberg is one of the greatest language
| architects and the C# team are continuing that tradition.
|
| I wish Anders was still in charge of C# :(
| nick_ wrote:
| Fingers crossed that he'll one day come back and make T#
| yeputons wrote:
| >How is it possible that I can write efficient and provably-safe
| code in C# without a degree in type theory?
|
| Because of two things mentioned in the article just below.
|
| > Here we see C#'s first trade-off: lifetimes are less explicit,
| but also less powerful.
|
| If C# is less powerful, it does not need powerful syntax. One
| does not need explicit lifetimes in Rust for a long time either,
| deduction work just fine.
|
| > The escape hatch: garbage collection
|
| If C# is ok with not tracking _all_ lifetimes _exactly_, it does
| not need powerful syntax. Not an option in Rust, by design.
|
| Basically, not all code is possible to write, and not all code is
| as efficient.
| daxfohl wrote:
| Both have "unsafe" escape hatches, so all code is possible to
| write efficiently. Just some cases are harder to prove correct
| in the type systems of each.
| sirwhinesalot wrote:
| The sort of "borrow checking" C# does is also similar to what the
| lifetime profile for C++ tries to do to catch bugs (but that's
| sadly a bit of a shitshow, existing C++ code is too much of a
| mess).
|
| A related idea are the concept of second class references, as
| exist in Hylo. There the "ref" is not part of the type, but the
| way they work is _very_ similar.
|
| Lifetimes give you a lot of power but, IMO, I think languages
| that do this should choose between either being fully explicit
| about them, or going "second class" like C# and Hylo and avoiding
| lifetime annotations entirely.
|
| Eliding them like Rust does can be convenient for experts but is
| actually a nightmare for newbies. For an example of a language
| that does explicit lifetimes without becoming unbearable, check
| out Austral.
|
| Instead of C#'s scope ref solution to having a function accept
| and return multiple references, another option (in an imaginary
| language) would be to explicitly refer to the relevant
| parameters:
|
| ref(b) double whatever(ref Point a, ref Point b) { return b.x; }
| pjmlp wrote:
| In theory, unfortunately VC++ and clang implementations since
| 2015 still leave too much off the table for us to fully rely on
| their lifetime static analysis.
| nickitolas wrote:
| You're correct that as far as I understand it the analysis
| propsoed by the C++ Lifetime Safety Profile is similar in many
| ways, however I think there's a few important distinctions with
| these C# features that are not directly related to the
| analysis: The C++ safety profilers are trying to be backwards
| compatible with as much C++ code as possible. Whereas my
| understanding is most of what's talked about in this post is
| sort of a clean break from idiomatic C#, and is not changing
| the semantics or adding new warnings to any pre-existing code.
| Another difference is that C++ obviously does not have a built
| in runtime GC, so the situations mentioned in this post that
| get "fixed" by GC heap allocation would remain an issue.
| sirwhinesalot wrote:
| Yes, I only meant that the way they work (meaning how the
| "lifetimes" are tracked) is very similar.
|
| C++ has to be "best effort" because it tries to bolt these
| semantics onto the pre-existing reference types, which were
| never required to adhere to them. It can catch some obvious
| bugs but most of the time you'll get a pile of false
| positives and negatives.
| PoignardAzur wrote:
| Cool article!
|
| Quick nitpick: the find example could return a reference to a
| static variable, and thus avoid both the heavy syntax and the
| leaked allocation:
|
| https://play.rust-lang.org/?version=stable&mode=debug&editio...
| Arnavion wrote:
| It doesn't even need to be a `static`. It can be `const` or
| just inlined `&0` because borrows of consts get promoted to
| `'static`
| raverbashing wrote:
| > How is it possible that I can write efficient and provably-safe
| code in C# without a degree in type theory?
|
| Excellent question
|
| And I feel that Rust, by making it explicit, makes it harder and
| unergonomic on the developer
| Decabytes wrote:
| My biggest issue with C# is that it doesn't have a good cross
| platform GUI. Maui dev is too slow and lacks a lot of the
| features a lot of people want, and Avalonia still uses the hybrid
| axml approach that just feels bad. I wish C# had a Flutter like
| library that utilized C#'s Hot Reload features
| zigzag312 wrote:
| I agree. Avalonia tries too much to be like WPF, but in my
| opinion, WPF was two steps forward and one step back (mainly
| due to XAML).
| neonsunset wrote:
| You have Avalonia, Uno Platform and, yes, MAUI. Most cross-
| platform GUI frameworks are flawed regardless of the language.
| The ones .NET has are decent, with various ways to approach the
| UI wiring - you have both XAML and declarative SwiftUI-style
| (and with MVU pattern too) options. Avalonia even has F#
| support through FuncUI. There are also plenty of bindings for
| SDL2, there's GTK# successor Gir.Core.
|
| I wish the comments focused more on the subject of the article
| which is interesting and under-discussed.
| kkukshtel wrote:
| Using something like Photino (https://www.tryphotino.io) with
| Blazor can start to feel like an actual good C# cross plat GUI
| solution but even as a C# truther I agree with you.
|
| I think you'll start seeing a lot more "cross platform C#
| frameworks" when PanGUI drops: https://pangui.io
|
| It's a native layout/gui util by the devs of the mega-popular
| Odin extension in Unity, and the idea is to directly solve
| "good native c# gui lib" with the implementation just being a
| single shader and an API that is more like DearIMGUI.
|
| I'm also planning on using it my own small 2D C# engine when
| it's available: https://github.com/zinc-framework
|
| I already do iterative hot reload GUI with DearImGUI in that
| engine so PanGUI will work in the same way.
| noveltyaccount wrote:
| Photino sounds almost identical to Maui Blazor Hybrid. Do you
| know the difference?
| aero_code wrote:
| Besides the other GUI libraries people have mentioned, there is
| also Eto: https://github.com/picoe/Eto
| not2b wrote:
| For the first example, for C or C++ code gcc will catch the
| dangling reference/pointer and warn about it, and since my group
| normally turns on -Wall -Werror, it's an error. However, the
| analysis is local and not as powerful as Rust's borrow checker.
|
| Try int* bug() { int longlived = 12; int* plonglived =
| &longlived; { int shortlived = 13; plonglived = &shortlived; }
| return plonglived; }
|
| With gcc -Wall -Werror
| akira2501 wrote:
| > The defaults can also be unintuitive: say we wanted to write a
| method on a struct which returns a reference to one of the
| struct's members.
|
| Why would you do that?
|
| > n fact, this is so common that Rust doesn't require you to
| write the lifetimes explicitly
|
| This is an actual _pattern_? Yikes^2.
| 0x457 wrote:
| > Why would you do that?
|
| a getter?
|
| > This is an actual _pattern_? Yikes^2.
|
| wat.
| akira2501 wrote:
| > a getter?
|
| Getters return values. This returns a pointer. So it's an
| accessor. With unchecked semantics. It's bizzare to me that
| anyone would use this technique. It's all downside with no
| upside.
|
| > wat.
|
| I'm expressing surprise that anyone would do this. I'm sure
| you were capable of understanding that.
| 0x457 wrote:
| > Getters return values. This returns a pointer. So it's an
| accessor. With unchecked semantics. It's bizzare to me that
| anyone would use this technique. It's all downside with no
| upside.
|
| When I use getter, I want to see the value of a field. I
| don't want an owned copy of said value, I just want to look
| at it, so returning reference makes _a lot more_ sense than
| returning a copy. In example it uses `i32`, but that's just
| for readability.
|
| > I'm expressing surprise that anyone would do this. I'm
| sure you were capable of understanding that.
|
| Yes, and I'm expressing surprised that you think it's bad.
| I'm not even sure what is bad? Lifetime elision that is
| well documented and works in a non-ambiguous manner? Using
| references instead of values? Do we need to memcpy
| overything now to please you?
| akira2501 wrote:
| > I want to see the value of a field. I don't want an
| owned copy of said value, I just want to look at it, so
| returning reference makes _a lot more_ sense than
| returning a copy.
|
| You can look at it with an owned copy. What is the issue?
| Is premature optimization the default mode in writing
| Rust? You don't see the issues with this?
|
| > I'm expressing surprised that you think it's bad
|
| You're surprised that someone simply has a different
| opinion? Your reaction failed to convey that.
| 0x457 wrote:
| > You can look at it with an owned copy. What is the
| issue? Is premature optimization the default mode in
| writing Rust? You don't see the issues with this?
|
| uhm, common sense isn't a premature optimization.
| Avoiding a needless copy is the default mode in writting
| rust and any other language.
| lowleveldesign wrote:
| I feel that nowadays Rust is the language to go when you are
| doing system programming, but C# is not a bad choice either. With
| .NET 9 being released in a few weeks we will get NativeAOT
| (compilation to a native single binary) for x86 (x64 and ARM64
| are already available). At work, I'm writing patches for legacy
| apps and needed to use C++ for most of my tasks. Nowadays, I'm
| doing more and more stuff in C# and enjoying it. For WinAPI there
| is a fantastic cswin32 [1] project that generates FFIs signatures
| on the fly. And it's fairly simple to extend it for other Windows
| libraries (I did it for detours [2], for example). And using
| pointers or working with native memory blocks is straightforward
| and intuitive for people with C/C++ background.
|
| [1] https://github.com/microsoft/CsWin32
|
| [2]
| https://lowleveldesign.wordpress.com/2024/07/11/implementing...
| starquake wrote:
| Although NativeAOT sounds cool and it's better than nothing: I
| don't like that it comes with a bunch of limitations[1]. I
| would have loved this if you could just use it without any
| changes, but I'm very worried that at some point I used
| something that prevents me from getting it to work and I have
| to figure out which limitation I just walked into. Correct me
| if I'm wrong.
|
| [1] https://learn.microsoft.com/en-
| us/dotnet/core/deploying/nati...
___________________________________________________________________
(page generated 2024-10-31 23:01 UTC)