Ads work around lack of autoplay by loading massive image filmstrips containing video frames (often split across hundreds of image files), and animating that in JS or CSS. This has all the annoyance of autoplay, with even worse bandwidth and memory usage.
Using individual components instead of the whole big framework isn't necessarily a failure. The components are designed to be usable independently.
TinyGlade project started a few years ago, when Bevy's rendering was even more basic. The art style is central to TinyGlade's appeal, so it's not surprising they've built a custom renderer.
DRM is really about control. It's a technical trick that thanks to DMCA anti-reverse engineering clauses becomes a legal trick to dictate exactly who and how can play the content, much tighter than what copyright and consumer laws allow by default.
For example, without DRM you couldn't effectively sell separate licenses for computer screens and TVs, because users could just connect their computer to a TV.
DRM allows negotiating everything about distribution, up to who pays who for having a button on the TV remote.
Those who control the DRM have a veto power over everything, and have it viciously enforced internationally thanks to it being tied to copyright.
What are you talking about? You can connect your computer to a TV just fine. No, lost sales are not 'just a convenient excuse', the sales they lose to piracy are far more numerous than the ones they'd gain with this fictional system that relies on people being willing to throw away money for no reason. 'It's about control' is a favorite element of conspiracy theories but corresponds to no real-world corporate need.
Rust ecosystem solves that by providing packages that are thin wrappers around underlying APIs. It's very similar to providing an .h file with extra type information, except it's an .rs file.
Correctness of the Rust wrapper can't be checked by the compiler, just like correctness of C headers is unchecked, and it just has to match the actual underlying ABI.
The task of making a safe API wrapper can be relatively simple, because you don't have to take into consideration safety an application as a whole, you only need to translate requirements of individual APIs to Rust's safety requirements, function by function. In this case you would need to be aware that the function call may unwind, so whether someone making a dedicated safe API for it would think of it or not, is only a speculation.
I seem to remember a linux kernel dev quiting and not being able to specify exactly what you say this wrapper should abide by as being a contributing factor.
If those specifications were written down clearly enough then this dev wouldn't have needed to spend 5 days debugging this since he spent a significant amount of time reading the documentation to find any errors they are making that is mentioned in the documentation.
And don't say that they can actually just read the rust code and check that since well, I can't read low level rust code and how any of the annotations ca interact with each other.
A single line of rust code could easily need several paragraphs of written documentation so that someone not familier with what rust is specifying will actually understand what that entails.
This is part of why Rust is difficult, you have to nail down the specification and a small change to the specification causes broad changes to the codebase. The same might need to happen in C, but many times it doesn't.
That Linux drama was due to "nontechnical nonsense" of maintainers refusing to document their APIs requirements.
In C you can have a function that returns a pointer, and get no information how long that pointer is valid for, what is responsible for freeing it, whether it's safe to use from another thread.
That's not only an obstacle for making a safe Rust API for it, that's also a problem for C programmers who don't want to just wing it and hope it won't crash.
The benefit of safe wrappers is that as a downstream user you don't need to manually check their requirements. They're encoded in the type system that the compiler checks for you. If it compiles, it's safe by Rust's definition. The safety rules are universal for all of Rust, which also makes it easier to understand the requirements, because they're not custom for each library or syscall. The wrappers boil it down to Rust's references, lifetimes, guards, markers, etc. that work the same everywhere.
Fisheries are a significant part of Iceland's economy, though.
Well-off European states with lots of sea (Norway, Iceland, Greenland, the UK) are disproportionately likely to reject EU membership due to the Common Fisheries Policy.
It's too bad that the EU failed to recognise, way back in the EEC days, that having all these countries in the EU was far more important than some fish. (And just wait to see what a huge problem the Common Agricultural Policy is going to be for Ukrainian membership.)
Ukrainian membership will hopefully never happen (unless Ukraine actually shapes up into a modern country first). Without the war the process would never have gone as far as it has already.
What makes you think that the reforms that have been going on since the war began aren't evolving Ukraine into a "modern" country similar to Romania, or Bulgaria, or Slovakia, or any of the post-Soviet members?
Not OP, but I think there's a valid argument about how "modern" those countries are. It's generally accepted that Romania and Bulgaria were let into the EU somewhat early, mainly for geopolitical reasons (foreclosing Russian influence there, cutting off Russia from the West Balkans). They are countries that continually lie on the tail end of every EU statistic, and have relatively unstable domestic politics and rule of law.
Personally, I'd like to see Ukraine in the EU asap, but I'd also like the EU reformed into a democratic federation, such that the equivalent of the FBI has meaningful authority to investigate and prosecute corruption and rule of law irregularities in member states like Ukraine and Bulgaria.
Entirely agree on what you would like to see. My point is that Ukraine is no worse than any of those nations, so admitting them to the EU is hardly going to affect the EU in any bad way.
Integration would be difficult on the agricultural front, but is something that the EU has been dealing with since inception.
One of the "benefits" of the war and the Ukrainian diaspora throughout Europe is that they will be able to integrate much faster than the previous Balkan and Balkan-adjaent countries as integration into Schengen would be able to be rapid.
Ukraine's engineering, mining, and agricultural industries would bring major advantages to the EU as a Union, diversifying engineering from dependence on Germany, and bringing broadscale agricultural efficiencies to the EU as well.
> integration into Schengen would be able to be rapid.
If there is one thing that Western EU countries have learned over the last 30 years, is that borders should be opened very slowly. With the current "black wave" sweeping the continent, there is no chance in hell that Ukraine will be admitted to Schengen in less than 10-15 years - if at all.
I'm not trying to rain on your parade, I'm just being realistic. Ukraine has all the problems of countries that were admitted to the EU too quickly, plus all the problems coming from scale and an agrarian economy. Just waving it in the EU would be a repetition of all the major missteps of the last 30 years.
> there is no chance in hell that Ukraine will be admitted to Schengen in less than 10-15 years - if at all.
The main concerns about new states joining Schengen are usually that (a) there may be too many immigrants from the new member to the old members, and (b) that the external Schengen boundaries will be insufficiently protected by the new member state.
Neither problem applies to Ukraine - Ukrainians have had essentially free migration access to the Schengen since 2022, and Ukraine's borders with third-party states would be - by far - the best defended. Ukraine could join Schengen very easily (unlike the EU itself, which may indeed take years). I wouldn't be surprised if Ukraine becomes part of Schengen well before it joins the EU, á la Iceland.
Hopefully this war will be over very soon, and soon after there will be a boom of children born in Ukraine, as happens after wars.
They'll have all sorts of issues, but has there ever been a nation that has been so unanimous about wanting to join?
I think NATO membership is more likely to occur before EU membership, Ukraine won't accept anything less for their future protection, having fought the Russians to a standstill.
EU integration is more about the economics first. For example, Ukraine just cut off traffic of Russian oil/gas across its territory. That was a source of income for Ukraine that will have to be compensated after the war. Perhaps via damages paid by seized Russian assets.
What I hope that both of these perhaps decade long paths start in 2025.
It would make a good celebration in 2035 if both of those things have happened.
NL fishing fleet has been modernized twice over. First cleaner engines, then electric pulse fishing (not nearly as bad as dragnet fishing).
French fishers don’t want to make the investments into their fleet, so they harassed French politicians until those spiked the pulse fishing research permits. This had the by-effect of (nearly) bankrupting the Dutch fishing fleet.
So no, fishing industry fights are not just an UK thing.
rustfmt uses 100-char lines by default (and can be configured to fill more), but that's not the problem with it.
The problem is that as soon as a whole statement doesn't fully fit on a single line, rustfmt switches from "horizontal" to "vertical" strategy. It then stops caring how much vertical space it takes, inserts line breaks as often as the style allows, and won't try to use the available line width.
You end up with long runs of finely chopped lines that contain only a single variable or a single call (often with each argument on a separate line too), which looks like having a 20-char line length limit.
It's either fully oneliner `foo().bar().baz()` or fully vertical
foo()
.bar()
.baz();
and you can't have anything in between. It will fight you if you put two calls on the same line.
- There are only two strategies and the algorithm to choose between them is trivial for a human to compute. This makes for way better readability, you can reliably predict where the next call/argument is going to be positioned.
- Refactoring becomes easier - moving an argument is now a simple `Line up` editor action.
- Source control diffs become more stable across changes.
...but it's hard to see the benefits on trivially simple examples like the one you presented. Here's a reformatting I did [1] to illustrate this:
Original:
ReturnType<SomeOther<S, TypeConstructor<Nested<S, T, U>, T>>, U, HmmLetsAdd<V, W>>
You rename a method, now it's a few chars shorter/longer, and some 1-liner call sites can become completely rewritten to the vertical style or vice versa.
You delete a single line, now the expression can fit under a threshold, and the whole block is spaghettified (especially terrible when deleting a struct field).
You wrap code in an if{}, which could have been a clean whitespace-only change for the entire body, but the indentation affects rustfmt's char counts, and it will now force different formatting decisions all over the place.
If you're changing an infallible method to a fallible one, you could have added error handling/fallback on separate lines, keeping changes to the happy path minimal – but not with rustfmt.
For minimal diffs you'd want something with a slack like gofmt, but rustfmt completely overlooked what makes gofmt so great and widely acceptable, and is a misanthropic ruthless canonicalizer instead, which ruins lots of use-cases, including minimal diffs.
Rustfmt's heuristics aren't simple. They're based on char counting of AST nodes, with multiple different limits for different node types, and crossing any threshold can trigger an avalanche of reformatting. There are more complex rules for closures and comments.
These heuristics are a source of formatting inconsistencies, e.g. each match arm is evaluated independently. Even when all match arms contain the same code, they can be formatted wildly differently due to patterns/name lengths pushing different thresholds. Indentation and AST context can make the same expression look differently each time.
Such problems largely don't exist in gofmt, so it's not a problem of code formatters, it's a problem of rustfmt specifically.
This is an inaccurate generalization of both C and Rust ecosystems, and an inflammatory framing.
openssl has made several rounds of incompatible changes. ffmpeg and related av* libs make major releases frequently. libx264 is famous for having "just pin a commit" approach to releases.
It's common for distros to carry multiple versions of major libraries. Sometimes it's a frequent churn (like llvm), sometimes it's a multi-year migration (gtk, ncurses, python, libpng).
C libraries aren't magically virtuous in their versioning. The language doesn't even help with stability, it's all manual painstaking work. You often don't see these pains, because distro the maintainers do heroic work of testing the upgrades, reporting issues upstream, holding back breaking changes, and patching everything to work together.
----
Cargo packages almost never pin specific dependency versions (i.e. they don't depend on exact minor/patch version). Pinning is discouraged, because it works very poorly in Cargo, and causes hard dependency resolution conflicts. The only place where pinning is used regularly is pairs of packages from the same project that are expected to be used together (when it's essentially one version of one package, but had to be split into two files for technical reasons, like derive macros and their helper functions).
By default, and this is universally used default, Cargo allows semver-major-compatible dependency upgrades (which is comparable to sover). `dep = "1.2.3"` is not exact, but means >=1.2.3 && <2.0.0. The ecosystem is quite serious about semver compatibility, and there is a tooling to test and enforce it. Note that in Cargo the first non-zero number in the version is the semver-major.
> ffmpeg and related av* libs make major releases frequently.
True, not every lib keeps long-term stable API.
> It's common for distros to carry multiple versions of major libraries. Sometimes it's a frequent churn (like llvm), sometimes it's a multi-year migration (gtk, ncurses, python, libpng).
This is consistent with what i wrote as these are changes in major versions.
> C libraries aren't magically virtuous in their versioning.
My statement was not categorical statement about C libraries, but it was about incentives shaping the ecosystem. Using libraries has costs and benefits. Frequent churn means higher costs, so it is acceptable when bringing higher benefits. In the C ecosystem, the cost for major version churn (or general library API incompatibility) is high, so people avoid it when possible. If sophisticated tooling like Cargo makes building against specific versions easy, it lowers the cost of churn, so everyone is less concerned with it.
It is true that in C ecosystem, keeping stability is not effortless, for software developers it means running CI with tens of distros / distro versions, to test their software against diversity of library versions. Not 'just use my Cargo.lock' (i am not sure how widespread this approach is in Rust ecosystem, but at least some people argues for it in this discussion).
A borrow checker that isn't "viral all the way down" allows use-after-free bugs.
Pointers don't stop being dangling just because they're stashed in a deeply nested data structure or passed down in a way that [[lifetimebound]] misses. If a pointer has a lifetime limited to a fixed scope, that limit has to follow it everywhere.
The borrow checker is fine. I usually see novice Rust users create a "viral" mess for themselves by confusing Rust references with general-purpose pointers or reference types in GC languages.
The worst case of that mistake is putting temporary references in structs, like `struct Person<'a>`. This feature is incredibly misunderstood. I've heard people insist it is necessary for performance, even when their code actually returned an address of a local variable (which is a bug in C and C++ too).
People want to avoid copying, so they try to store data "by reference", but Rust's references don't do that! They exist to forbid storing data.
Rust has other reference types (smart pointers) like Box and Arc that exist to store by reference, and can be moved to avoid copying.
> Pointers don't stop being dangling just because they're stashed in a deeply nested data structure or passed down in a way that [[lifetimebound]] misses
This is the typical conversation where it is shown what Rust can do by shoehorning: if you want to borrow-borrow-borrow from this data structure and reference-reference-reference from this function, then you need me.
Yes, yes, I know. You can also litter programs with globals if you want. Just avoid those bad practices. FWIW, references break local reasoning in lots of scenarios. But if you really, really need that borrowing, limit it to the maximum and make good use of smart pointers when needed. And you will not have this problem.
It looks to me like Rust sometimes it is a language looking for problems to give you the solution. There are patterns that are just bad or not adviced in most of your code and hence, not a problem in practice. If you code by referencing everything, then Rust borrow-checker might be great. But your program will be a salad of references all around, which is bad in itself. And do not get me started in the refactorings you will need every time you change your mind about a reference deep somewhere. Bc Rust is great, yes, you can do that cool thing. But at what cost? Is it even worth?
I also see all the time people showing off the Send+Sync traits. Yes, very nice, very nice. Magic abilities. And what? I do my concurrent code by sharing as little as possible all the time. So the patterns of code where things can be messed up are quite localized.
Because of this, the borrow checker is basically something that gets a lot in the way but does not add a lot of value. It might have its value in hyper-restricted scenarios where you really need it, and I cannot think of a single scenario where that would be really mandatory and really useful for safety except probably async programming (for which you can do structured concurrency and async scopes still in C++ and I did it successfully myself).
So no, I would say the borrow checker is a solution looking for problems because it promotes programming styles that are not clean from the get go. And only in this style it is where the borrow checker shines actually.
Usually the places where the borrow checker is useful has alternative coding patterns or lifetime techniques and for the few ones where you really want something like that, probably the code spots are small and reviewable anyway.
Also, remember that Rust gives you safety from interfaces when you use libraries, except when not, bc it basically hides unsafe underneath and that makes it as dangerous as any C or C++ code (in theory). However, it should be easier to spot the problems which leads more safety in practice. But still, this is not guaranteed safety.
The borrow checker is a big toll in my opinion and it promotes ways of coding that are very unergonomic by default. I'd rather take something like Swift or even Hylo any day, if it ever reaches maturity.
In general I view the borrow checker as a good friend looking over my shoulder so I don't shoot myself in the foot in production. 99 times out of 100 when the borrow checker complains it's because I did something stupid/wrong. 0.99 times out of 100 I think the borrow checker is wrong when I am in fact wrong.
0.01 times out of 100 the borrow checker fumbles on a design pattern it maybe shouldn't so I change my design. Usually my life is way better for changing the design after anyways.
The thing is, you don't need to have refs of refs of refs of refs of refs. You can clone once in a while or even use a smart pointer. You'll find in 99.99% of cases the performance is still great compared to a GC language. That's a common issue for certain types of people learning how to write Rust. I can't think of any application that needs everything to be a reference all the time in Rust.
As far as "mandatory" goes for choosing a language. We can all use ASM, or C, write everything from scratch. It's a choice. Nothing is mandatory. No one is saying you HAVE to use Rust. Lots of people are saying "when I use it my life is way better", that's different. There was a recent post here where people say they don't use IDE's with LSP or autocomplete. A lot of people are going to grimace at that, but no one is saying they can't do that.
> I also see all the time people showing off the Send+Sync traits. Yes, very nice, very nice. Magic abilities. And what? I do my concurrent code by sharing as little as possible all the time. So the patterns of code where things can be messed up are quite localized.
They check whether your code really shares as little as you think, and prevent nasty to debug surprises.
The markers work across any distance, including 3rd party dependencies and dynamic callbacks, so you can use multi-threading in more situations.
You're not limited to basic data-parallel loops. For example, it's immensely useful in web servers that run multi-threaded request handlers that may be calling arbitrary complex code.
> places where the borrow checker is useful has alternative coding patterns
There's a popular sentiment that smart pointers make borrow checker unnecessary, but that's false. They're definitely helpful and often necessary, but they're not an alternative to borrow checking.
Rust had smart pointers first, and then added borrowing for all the remaining cases that smart pointers can't handle or would be unreasonable to use.
Borrowing checks stack pointers.
Checks interior pointers to data nested inside of types managed by smart pointers (so you don't have to wrap every byte you access in a smart pointer).
It allows functions safely access data inside unique_ptr without moving it away or switching to shared_ptr.
Prevents using data protected by a lock after the lock has been unlocked. Prevents referencing implicitly destroyed temporary objects. Makes types like string_view and span not a footgun.
> the borrow checker is basically something that gets a lot in the way
This is not the case for experienced Rust users.
Borrow checker is a massive obstacle to learning and becoming fluent in Rust. However, once you "get" it, it mostly gets out of the way.
Once you internalise when you can and can't use borrowing, you know how to write code that won't get you "stuck" on it, and avoid borrow checking compilation errors before they happen. And when something doesn't compile, you can understand why and how to fix it. It's a skill. It's not easy to learn, but IMHO worth learning more than C++'s own rules, Core Guidelines, UB, etc. that aren't easy either, and the compiler can't confirm whether you got them correct.
> Borrowing checks stack pointers. Checks interior pointers to data nested inside of types managed by smart pointers (so you don't have to wrap every byte you access in a smart pointer). It allows functions safely access data inside unique_ptr without moving it away or switching to shared_ptr. Prevents using data protected by a lock after the lock has been unlocked. Prevents referencing implicitly destroyed temporary objects. Makes types like string_view and span not a footgun.
I understand part of the value the borrow checker brings. Actually my complaint it is more about having a full borrow checker and viralize everything than about having the analysis itself. For example Swift and Hylo do some borrow-checking analysis but they do no extend that to data structures and use reference counting (with elision I think) and value semantics.
The problem with the borrow checker is not the analysis. It is the virality. Without the virality you cannot express everything. But with the amount of borrow checking that can be done through other conventions (as in Hylo/Swift) and leaving out a part of the story I think things are much more reasonable IMHO.
There are so many ways to workaround/just review code in(assuming the cases left are a bunch of those) the remaining spots that presenting a fully viral borrow checker to be able to represent so many situations (and on top of that promoting references everywhere, which breaks local reasoning) that I question the value of a full borrow checker with full virality. It also sets the bar higher for any refactoring in many situations.
> Borrow checker is a massive obstacle to learning and becoming fluent in Rust. However, once you "get" it, it mostly gets out of the way.
This is just not true for many valid patterns of code. For example, data-oriented programming seems to be a nightmare with a borrow checker. Linked structures are also something that is difficult. So it is not only "getting it", it is also that for certain patterns it is the borrow checker who "gets you", in fact, "kidnaps you away" from your valid coding patterns.
> but IMHO worth learning more than C++'s own rules, Core Guidelines, UB, etc
I admit to be more comfortable with C++ so it is my comfort zone. But there are middle solutions like Swift or (very experimental) Hylo that are worth a try IMHO. A full, embedded borrow checker with lifetime annotations is a big ergonomy problem that brings value if you abuse references, but when you do not, the value of the borrow checker is lower. Same for escaping references several levels up... why do it? I think it is just better to try to avoid certain coding patterns. Not because of Rust itself. Just as general coding style in any language...
> that aren't easy either, and the compiler can't confirm whether you got them correct.
Not all as of today, but a subset yes, there are linters. Also, there is an effort to incrementally increase the value of many analysis. It will never be as perfect as Rust's, I am sure of that. But I am not particularly interested either. What I would be more interested in is if with what can be fixed and improved the delivered software has the same defect rates as Rust lifetime-wise. This is counter-intuitive bc it looks like the better the analysis, the better the outcome, but here two factors also play the game IMHO:
1. not all defects are evenly distributed. This means that if the things that can be lifetime-checked are a big amount of typical lifetime checks in C++, even if not all kinds such as Rust's can be done, it can get statistically very close.
2. once the spots for unsafe code are more localized, I expect the defects rate to decrease more than linearly, since now the attention is focused on fewer code spots.
Let us see what comes from this. I am optimistic that the results will be better than many people predict in ways that look to me too academic but without taking into account other factors such as defect density in clusters and reduction of surface to inspect by humans bc it cannot be verified to be safe.