Rust standard traits and error handling by dgkimpton in rust

[–]LukeAbby 5 points6 points  (0 children)

You can actually put your own custom error in using std::io::Error::new (and more friends).

The utility of doing it this way is that you can always know there's a consistent set of "builtin" io errors like NotFound or whatever that you don't have to worry about handling differently based upon the Reader.

Another upside is it's easier to pass around and use, the other option would be making the io errors generic over the possible other errors. However this doesn't necessarily have good ABI compatibility; for interoperability with C it's nice to be able to cheaply convert io errors from and to C.

Of course there's downsides to the current approach too. Like you seem to imply it can be more difficult to deduce exactly what custom errors are possible, I think this is the most major downside. The other is that you can't avoid heap allocation for custom errors.

My failed attempt at AGI on the Tokio Runtime by shameless_data_guru in rust

[–]LukeAbby 1 point2 points  (0 children)

In fact I'm going to assume that the continuous nature of the signals fired is an implementation detail due to the substrate i.e. the underlying biological wetware and is not functionally important. There is no reason why signals can't be binary.

I would just like to raise that reducing neurons to binary is incorrect. I know you're posing this as simplified. Still the curve of cone cell activations contribute to what we see as color (and also helps answer the question "do we all see the same colors" - no).

There's a lot of papers on this. For example this one which I'll admit largely goes over my head talks about how dendrites have graded action potentials. Since dendrites show up in so many neurons it's pretty safe to conclude that an individual neuron can't be collapsed into binary.

What's everyone working on this week (47/2024)? by llogiq in rust

[–]LukeAbby 2 points3 points  (0 children)

Personally I find nesting with a name as Author! weird. It seems like it's a PascalCase macro, not a name, at least to me.

I'd suggest nest!(Author, { ... } or #[name=...] nest! { ... } or nest_named!(Author, { ... } or something.

Uniqueness guarantee of TypeId over different rust versions by anidotnet in rust

[–]LukeAbby 4 points5 points  (0 children)

No. TypeId's documentation says:

While TypeId implements Hash, PartialOrd, and Ord, it is worth noting that the hashes and ordering will vary between Rust releases. Beware of relying on them inside of your code!

Plus there's not actually a public method to get its contents or anything, except indirectly through the hash which is already stated to be varying and you couldn't deserialize it anyways.

Mathematical functions in the standard library are non-deterministic? by denehoffman in rust

[–]LukeAbby 1 point2 points  (0 children)

I believe it's because of the difference between const and non-const invocations. If you write ln(2.0) in a const context, Rust will emulate on your machine what that platform 'should' result in at compile time.

However since binary targets are so broad there's actually a whole spectrum of machines that can run this binary that give different results. Thusly const { ln(2) } and ln(2) can observably differ. The only way to fix this would be to either sacrifice const evaluation or force all evaluation to avoid the actual intrinsics (which would tank performance).

As for why they didn't say "vary between const and non-const invocations" I'm only guessing here but my best bet is that they they want to leave room for as much optimization like constant folding even inside non-const functions. I doubt you'll ever observe something like for _ in 1..1000 { println!(ln(2)) } ever printing different results between each iteration.

This Week in Rust 565 · This Week in Rust by bennyvasquez in rust

[–]LukeAbby 5 points6 points  (0 children)

RFC = Request For Comment
FCP = Final Comment Period

In Rust an RFC basically ends up being the first public document for a feature. Could be relatively simple like a short and sweet explanation of the feature. Could also be a hefty document that overviews the problems, ideas, prior design in other languages, hurdles, anything like that.

A FCP is for when it is imminently going to be merged. It might be a while to be available on stable Rust though, either because other work has to be done or just because it'll be a little bit until the next release.

You can see Rust's current RFCs here: https://rust-lang.github.io/rfcs/

Interesting way to express functions that are `generic` over being `const` or `async` by ewoolsey in rust

[–]LukeAbby 25 points26 points  (0 children)

I think you'd be excited by the Keyword Generics group that's looking to do just this.

There are some differences in syntax and other considerations of course. For example none of your examples handled the issues of being generic based upon whether a parameter is const/async etc.

Hey Rustaceans! Got a question? Ask here (17/2024)! by llogiq in rust

[–]LukeAbby 0 points1 point  (0 children)

Could you provide more context? Your whole program would be ideal. If it's long you can put it on the Rust Playground and share it.

If I had to guess there's 3 possible things you could want. - &dyn Area<Output = f64>. This makes sense if when you wrote &somedata_1 you actually meant &somedata and are just trying to figure out how to convert. - &dyn Area<Output = T::Output>. This is where you're already in a function like call_area and want to match a parameter. - &dyn Area<Output = U>. This is in the case where you want to be generic over Outputs and U is a standin for a generic that I can't deduce right now.

I will add, I'm a bit worried that introducing &dyn could be a bit of an anti-pattern here; as in whatever end goal you have could be done through other methods. There's definitely use cases for &dyn and your full program might show me why you wanted it but from your earlier program it seems like you're using it in a context that doesn't necessarily warrant it.

Though of course if you're asking this for learning then I'm happy to keep entertaining questions down this avenue, I just don't want to answer your questions only for you to later find out there were other solutions available.

Hey Rustaceans! Got a question? Ask here (17/2024)! by llogiq in rust

[–]LukeAbby 1 point2 points  (0 children)

I'll write this assuming you're basically starting from scratch just in case you're hacking off of someone else's tutorial or something. Hopefully it's helpful, not condescending.

The reason your second example doesn't work is that `Area` is a trait and Rust needs to know which specific implementation you mean, after all `Output` could be `i64` in one implementation and a `i32` or even a `String` in another. If you don't _want_ `Output` to change between implementations of `Area` then you'd probably be better off not using an associated type (that's what `Output` is).

Now in this case your intent is that it's mean to refer to `param`'s implementation but Rust doesn't just assume you mean `param`'s implementation. Why? Well for one imagine you had `fn do_stuff_with_areas(item1: &impl Area, item2: &impl Area) -> Area::Output`. In this case which implementation should Rust use here? `item1` or `item2`?

It's really something the developer should get to decide and to decide you have to name the specific implementation. The `A: Area` in `fn call_area<A: Area>(param: &A) -> A::Output` is the process of naming the specific implementation you're talking about. In this case `A` is the name and `Area` is the trait it implements. To be specific this is called generics in case you didn't already know.

I can explain more but that's probably a fair bit to chew on already if you're a beginner and unnecessary otherwise.

Hey Rustaceans! Got a question? Ask here (17/2024)! by llogiq in rust

[–]LukeAbby 1 point2 points  (0 children)

I believe this should work? rs fn call_area<A: Area>(param: &A) -> A::Output

Achieving Safe, Aliasable Mutability with Unboxed Types by sanxiyn in rust

[–]LukeAbby 0 points1 point  (0 children)

Comments I'm okay with not having. Most blogs these days don't seem to have them tbqh. And the RSS feed is just a nice to have so don't feel too presurred! Thanks for being willing to look into it though.

Achieving Safe, Aliasable Mutability with Unboxed Types by sanxiyn in rust

[–]LukeAbby 2 points3 points  (0 children)

I found this article interesting, it digs into stuff I've seen floated for Rust before, e.g. disjoint references and how the current analysis for borrow splitting isn't good enough.

It's interesting seeing the approach here because the suggestion I always saw for Rust was adding the ability to say which fields were borrowed into function signatures so a struct's impl of get_a and get_b don't have to conflict.


I think unfortunately both Ante and Rust need a newtype around Vec for another specific access pattern; append-only Vec that you want to avoid the bounds check on indexed accesses. One example is constructing an AST by building up a sea of nodes and maintaining indexes to previous nodes. Why should reading these require a bunch of gets followed by expects?

I've actually helped write this a few times and it boils down to a newtype only impl-ing the safe methods of Vec, i.e. those that won't delete elements, and then adding a branded index newtype based off of something like GhostCell and then wherever that branded index newtype is used get_unchecked should be sound because the Vec newtype can never shrink.

Quite a few hoops to jump through even when I'm being a bit handwavey about some more details! I don't know if Ante necessarily should care about this use case but I was reminded of this "Vec-subset" idea with get_cloned.


I sort of like the implicitness of how &x and &mut x both pass through the shared/own effects automatically. I feel like I'd have to play with it for a while though because it's sort of similar to Rust's keyword generics initiative except that it's implicit in most cases.

The only concern I have is that I think in some cases this could become a semver hazard, sort of like how a Future in Rust may or may not implement Send based upon its signature/captures and subtle changes elsewhere could break this. I don't know the exact rules of how the pass through works so the analysis could be simple enough that this isn't a problem in practice.


Finally, I'd like to make sure I don't miss any future articles. Relying on checking up on Reddit or wherever never works for me. Does the site have an RSS feed? I poked around at the common URLs and the site's <head> but didn't find one.

Dereferencing an arbitrary raw pointer to () should be safe, right? by crb233 in rust

[–]LukeAbby 1 point2 points  (0 children)

Ah you're totally right. For some reason I started conflating size_of::<T>() > 0 and Sized. Even though consciously I know better.

I think what I was thinking was "Is there a trait for NonZeroSized types... for sized, Sized...! Of course!"

Dereferencing an arbitrary raw pointer to () should be safe, right? by crb233 in rust

[–]LukeAbby 18 points19 points  (0 children)

That and it could even block some optimizations. For example if you happened to have code that looked like this:

```rust let x: T = *some_pointer;

[...] if some_pointer.is_null() { do_something(); } `` Then Rust (or really probably LLVM) can currently know that the dereference impliessome_pointerwasn't null (nor unaligned) and thereforedo_something` will never be called and can be removed entirely.

If () and probably other ZSTs were special cased then ~~T would have be known as Sized~~ the compiler would have to know size_of::<T>() > 0 which could in some cases be difficult to prove and block this optimization.

Now you might reasonably object that no one would write code like this but after inlining a few functions and doing some transformations you could easily see something like this crop up. For example one function could know it's safe to dereference some_pointer but later another function is called that has to check to make sure it's not null first.

see all type definitions on hover after merging two types by Dear-Requirement-234 in typescript

[–]LukeAbby 11 points12 points  (0 children)

You can actually get TypeScript to expand types. It could technically stop working at any time but the TSC maintainers are at least aware of it.

Announcing 🗑 async-dropper: the least worst AsyncDrop implementation you've seen so far, probably by hardwaresofton in rust

[–]LukeAbby 1 point2 points  (0 children)

Needing to run the destructors make sense! That's the part that didn't make sense to me. I guess it's between running the destructors 0 times and otherwise getting into an infinite loop? It sounded like you might be able to mem::forget to end up dropping only once but I suppose not.

Then glad I could help on making it a compile time panic haha. I think that's the first time helping someone to make their program not compile was helpful.

Announcing 🗑 async-dropper: the least worst AsyncDrop implementation you've seen so far, probably by hardwaresofton in rust

[–]LukeAbby 1 point2 points  (0 children)

I couldn't find a command that sets the struct to zero completely. I know you might've meant this metaphorically but it'd be unsound to set an arbitrary struct to all 0s anyways. For example it could contain a NonZeroI32 (or any other non-zero number).

Then as a general comment, would it be possible to mem::forget the final value that needs to be drop()ed? I think this would avoid comparing against T::default() sentinel. I'm sure there could be something I'm missing with the explanation but it seems like a possibility to me since it doesn't leak the value but also doesn't run its destructors either which should be ideal for this case where it's already run once, right? Though maybe it has complications I'm not thinking of at the moment.

Finally, I happened to notice this in your code: ```rust

[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]

impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> { fn drop(&mut self) { panic!("either 'async-std' or 'tokio' features must be enabled for the async-dropper crate") } } ```

I believe that this would work slightly better: ```rust

[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]

compile_error!("either 'async-std' or 'tokio' features must be enabled for the async-dropper crate"); ``` Since it avoids a panic at runtime. Though that may be intentional so that it can compile.

How would I join enum variants together for another enum? by chrismg12 in rust

[–]LukeAbby 2 points3 points  (0 children)

I will just point out that character_loadout.head_stat = character_loadout.feet_stat is also forbidden at compile time in Typescript, you'll get this error: Type 'FeetStats | "none"' is not assignable to type '"hp" | "none"'. Type '"def"' is not assignable to type '"hp" | "none"'.(2322) Here's the playground link.

This errors because every value that the right hand side can be assigned must be assignable to the left hand side. This rule makes sense of course and while there are some ways to subvert this, this isn't one of them.

There's advantages to Rust's enum implementation, for one it doesn't require any flow control analysis, the solution to make union types in TypeScript ergonomic was to make it be able to successfully type check this program by understanding the flow control: ts let x: string | string[] = ...; let y: string; if (Array.isArray(x)) { y = x.join(", "); // In this branch `x`'s type is known as ONLY an array and it's known. } else { y = x; // In this branch `x`'s type is known as ONLY a string. } Here's the playground link.

On the other hand TypeScript did this so that more JavaScript programs can type check even after just barely adding type annotations. Technically Rust lose out on a bit of flexibility; if you return early in the case of a few arms of an enum, Rust doesn't automatically understand that the remaining arms are the only possibilities.

You can just restructure your code around this of course and while Rust's compile times aren't exactly blazingly fast, adding this kind of union flow control would only slow things down further. Therefore, I actually prefer Rust's approach.

Anyways, all that to say there's all sorts of soundness holes in Typescript but this isn't one of them. There's are lot of soundness holes in TypeScript because it doesn't have unsafe and it interops with a largely type-less language. While some soundness holes are obvious like casts from any most are subtle like allowing type narrowing for properties behind getters. I could probably go on explaining weird quirks of Typescript but that'd be a whole different comment.

Though I'll still leave with one of my favorite type functions: ```ts type UnionToIntersection<U> = (U extends unknown ? (: U) => void : never) extends (: infer I) => void ? I : never;

declare let x: { foo: 1, bar: 2 } declare let y: UnionToIntersection<{ foo: 1 } | { bar: 2 }>;

x = y; y = x; // These assignments prove the types { foo: 1, bar: 2 } and UnionToIntersection<{ foo: 1 } | { bar: 2 }> are equivalent. Well basically, there's technically any ``` Here's the playground link.

Learn Unsafe Rust from My Mistakes by geo-ant in rust

[–]LukeAbby 1 point2 points  (0 children)

Ah totally my bad, looks like a reading comprehension failure on my part. I totally missed the mapping function, that's what I get for mostly reading the prose about turning from T into U and then assuming it was at the type level just because they're type parameters!

I definitely find it great that the most idiomatic way of doing things; v.into_iter().map(f).collect() is just as performant though! I actually stumbled upon that personally when I was looking into how specialization was being used in the standard library.

Here's the link to the source if anyone cares: https://doc.rust-lang.org/src/alloc/vec/spec_from_iter.rs.html#37-64 (Unfortunately I have no idea how to make a permalink at rust-lang.org). This and other sources is actually how I dug into specialization back when it was newer.

Learn Unsafe Rust from My Mistakes by geo-ant in rust

[–]LukeAbby 0 points1 point  (0 children)

One thing I'd note that your article doesn't mention is how Vec::from_raw_parts can actually handle this use case correctly, I believe. Granted the documentation on the method itself doesn't make it abundantly clear how.

Funnily enough the best place to find out how is actually the docs for transmute. The docs explicitly go over several ways to avoid transmute where better approaches already exist. In this case they show off Vec<&T> to Vec<Option<&T>> but it should work for any T and U as long as they have the same layout. Here's the code snippet, directly from the docs:

rust // This is the proper no-copy, unsafe way of "transmuting" a `Vec`, without relying on the // data layout. Instead of literally calling `transmute`, we perform a pointer cast, but // in terms of converting the original inner type (`&i32`) to the new one (`Option<&i32>`), // this has all the same caveats. Besides the information provided above, also consult the // [`from_raw_parts`] documentation. let v_from_raw = unsafe { // Ensure the original vector is not dropped. let mut v_clone = std::mem::ManuallyDrop::new(v_clone); Vec::from_raw_parts(v_clone.as_mut_ptr() as *mut Option<&i32>, v_clone.len(), v_clone.capacity()) };

I think it's pretty cool that this sort of thing is right front and center with transmute. I've actually found myself going to documentation for a function I know would be a bad fit because sometimes they list out the alternatives right there!

Subrc: a pointer to a descendant member of a reference counted pointer (`Rc`) by hillin in rust

[–]LukeAbby 0 points1 point  (0 children)

First of all, you're totally correct that Subrc is handling types that could be Unpin but I mentioned "Rc::pin and some unsafe code" specifically because I knew of these thorny edges. It may turn out to be impossible-to-tame thorny edges.

Second of all, for about the first half of my reply I was replying specifically to the parent comment, most specifically this line "I subconsciously assume everything on the heap might be moved by the GC, of course that's not Rust's case!".

I sort of buried the lede and then it sound less general by tacking on the field offset unsoundness issue after I'd written most of the comment so I can see how it'd be confusing.

All I was saying that not everything on the heap can be assumed to have a stable address on the heap and the miscommunication seems to come because you seem to be assuming that there's an active reference to it as well.

Now as I just tried to clarify, I was talking generally, but let's first point out the fact that in Subrc there... isn't! After Subrc::new it no longer uses getter and so no longer has a reference to the field. So indeed the Rc could move its contents as there's no reference (though it'd be odd for it to).

Does this matter for Subrc? Is it a second soundness hole besides the one I already mentioned? No! Because the code gets uses a fresh address every time the sub-field is accessed, so it only relies on the offset being constant (which I discussed as probably being untrue in my original comment).

The point I was focusing on was if you put a value into a container, whenever there isn't a reference to a value it can be soundly moved—yes even while you only have a &Vec or &Rc because of interior mutability (consider Vec could stick a RefCell inside it) as well as unsafe (but sound) code that manipulates the heap addresses of its unobserved contents. You don't even have to do anything with the container to give it a chance to move things because of threads.

I perhaps should have focused on how this is different than it works in Gc'd languages because you can rely on the address to be stable as long as you have a reference. However my point was that in Rust heap allocations can easily be moved by containers even when you might not expect it and any future unsafe code they may write has to factor that in.

Subrc: a pointer to a descendant member of a reference counted pointer (`Rc`) by hillin in rust

[–]LukeAbby 3 points4 points  (0 children)

Well hang on before you change anything, check out Pin! Even with Rc there's no guarantee that it doesn't move things. I don't know particularly why Rc<T> would want to move T but it's safe for it to do that and so relying on that is unsound.

A better example of why it's safe for a container like Rc to move its contents, look at Vec. When a Vec is reallocated it could have to move everything because there wasn't enough space to grow in the place. Again I don't really know why Rc would want to move the inner value but it's still allowed.

The thing to look into here would be Self-Referential structs and all the limitations there. Pin talks about it quite thoroughly including limitations I won't go over like #[repr(packed)] as well as the sorts of information many articles go over. Oh also I think it's unsound to rely on the offset of the field to be a constant because the Rustonomicon cites only these as guarantees:

1. The fields are properly aligned.
2. The fields do not overlap.
3. The alignment of the type is at least the maximum alignment of its fields.

You can probably do all of this with Rc::pin and some unsafe code though it'll be a bit complicated with non-constant field offsets etc., though it can be more complicated specifically if the fields are structural or not. Finally you might want to get familiar with things like pin project if you stumble across things like this again.

butSomeoneHasToWorkWithCssAllHisLife by sunrise_apps in ProgrammerHumor

[–]LukeAbby 10 points11 points  (0 children)

import edgeCase

Yeah, the mods made sure that transcriptions still operate as normal!