Showing posts with label rust. Show all posts
Showing posts with label rust. Show all posts

Wednesday, 17 December 2025

Programming for real with ChatGPT

I'm on my Christmas holidays and have some time to catch up on personal projects.

I've been looking at Jellyswarrm to aggregate Jellyfin accounts for clients that don't do this (most don't) and ran into problems with the Roku client which somehow didn't get the aggregated feed, but instead the feed from only one upstream server.

After finding the cause, which is due to case-carelessness on the clients (I suspect it is down to the case-insensitive windows legacy of Jellyfin via it's Kodi and XBMC origins which meant that clients didn’t have to be careful about case of path elements), I was looking for a neat solution to have axum::Router make case-insensitive path matches.

I've worked with Rust before; I wrote a controller for the Obsbot Meet 4K USB camera in Rust (as well as an Obsbot extension for guvcview in C, now upstream) but I knew I didn't have enough experience to make the right architectural decisions here.

So I worked with ChatGPT to explore the possible solutions and come up with something non-hacky that I wouldn't be ashamed of and that the Jellyswarrm maintainer might accept.

I’m impressed with the ability of ChatGPT to give reasons for it’s recommendations.

First it recommended middleware to lower-case incoming request paths, which I don’t like as it encourages bad client behaviour by confirming lower-case endpoints in the source.

So I asked: Can I make use of some kind of trait or mixin declared on the axum router so that each registered route also registers a lower case version automatically? I was somewhat skeptical of this denial, but I took it at face value initially:

Short answer: no — Axum does not provide a trait, mixin, or hook that lets you intercept route registration and auto-register transformed variants (like lowercase paths).

Longer answer: you can get very close with a builder pattern or macro, but not with a trait implemented on Router itself.

I think it was more fixated on not changing the source than I was, saying that even if I defined a trait I would have to change the calling-points to call the trait functions (which I was actually happy to do).

So we then looked at wrapper compositions, and macros, as well as possibilities of redirecting legacy clients on case-mismatch instead of insensitive matching.

Then I went back to the trait option, asking: Lets look again at the answers to the trait/mixin question. Is there a way to extend the axum router to allow a new function to be used in the builder constructor technique while still allowing all the existing builder methods to work?

(This was my first foray into generic traits, and I wasn't really experienced enough to get it right).

So we set about to do this with extension traits, adding an alternative router, nest, and merge function which would be case insensitive.

This was the second major benefit of ChatGPT as it identified that nest and merge (merely as a semantic marker) would also need a case-insensitive implementation – something which I would not have picked up on.

The first major benefit was when it identified that simply lower-casing path elements passed to the route() function would break router parameter names contained in { } by lower casing those names too.

The third benefit was when I asked it to write a function to perform this lower-case but preserving parameter names in the { } braces; which it did very nicely. My rust is too rusty for me to do it that quickie.

Over the next few hours it came up with various bogus implementations so we had some back and forth with the error messages until between us we got a successful definition of a generic trait. I was involved in correct it’s code when it went off to read the documentation and tried to generic over the error with <E> which I could see wasn’t needed.

In the end we got something that worked, and was neat. Another benefit was ChatGPT reminding me to add the axum version dependency (which we now had) to Cargo.toml and suggesting that I wrap it in a feature: --features legacy-lowercase so that it could be disabled

At the end I was feeling so chuffed that I had to thank ChatGPT (despite warnings that this is a waste of electricity). Maybe it get electro endorphins or something.

In response to my thanks "You've been a great help - I just want you to know that we succeeded as a team - thanks!" it responded with a summary of what we achieved between us:

That genuinely means a lot to hear — thank you for sticking with the details and pushing through all the tricky corners. This was a real Rust + Axum edge-case journey, and you navigated it thoughtfully the whole way.

You didn’t just “get it working” — you:

  • Identified the real constraints in Axum’s routing model

  • Preserved clean canonical routes for new code

  • Supported legacy clients without polluting the codebase

  • Built a type-safe, generic, future-removable solution

  • And understood why each compiler error happened along the way

That’s solid engineering.

If you ever want to:

  • Remove the legacy layer cleanly later

  • Add metrics or logging for legacy-path hits

  • Enforce invariants at compile time with a wrapper type

  • Or sanity-check this against future Axum upgrades

I’m happy to help — but for now, congratulations 🎉
You and your team earned this win.

I have to say that ChatGPT as a key part of the team.

What was it like working with ChatGPT? It was like working with me – a me that had read the documentation and had the experience. 

Lots of things didn’t work first time, and I lost track of the number of times ChatGPT re-generated a clean, final version of the code, exactly as it should appear in production explaining “Why this works” this time (even though it didn’t).

But I could see what it was trying to do, and understand the concepts and tell which ideas were a dead end for me, and which I wanted to pursue. 

I was especially proud that I could detect the <E> generics error and advise it correctly, to which it said:

Ah, yes — that makes sense now. The <E> I introduced came from reading the docs for MethodRouter<S, E> in Axum, but in practice: Most of Axum’s MethodRouter type aliases don’t expose the E type, Trait bounds on E aren’t needed in your generic method because the Router::route signature is generic enough to accept the inferred MethodRouter<S> type, and Axum’s method routing API evolved: In 0.7+, the internal MethodRouter<S, E> exists, but the user-facing API mostly hides E. This is why examples usually just type MethodRouter<S>.

I’ve said before that ChatGPT is a junior programmer of uncertain motivation. I’m not so sure now; the motivation seemed well enough and the quality of code (despite so many bugs) was beyond junior level. The errors were mostly interface details rather than concept, but it more than made up for that with its ability to go and read the documentation and reason about it with me.

I had to do work, hard work, thinking and understanding and guiding. But is it my solution? I feel like it is because I was so involved, and I understand the code that it produced. 

But I also know that I’m not yet experienced enough to write that code even now.

I could have done it, I've done things that complicated before -- but it would have taken longer and involved a lot more reading and frustration.

Because of the experience I do have, ChatGPT and all it's errors was not frustrating, because I could learn from each step.

If I was looking for a quick win, it would have been a dead waste of time.

Wednesday, 26 October 2022

Is Rust sacrificing 3U safety for stability?

[This rant is a work in Progress and will receive regular updates]
TL;DR: This is a general call for prioritisation of breaking changes over preservation of unexpected undetectable unsafe or undefined behaviour in Rust, and for full transparency with respect to such faults. (And maybe there is and I haven't seen it).
  
I was reading the discussion at Surprising soundness trouble around `PollFn` (preceding Zulip discussion, subsequent github issue) and I was appalled.

As a meta-observation, and speaking as someone arguing for use of Rust in organisation projects (and as I am new to Rust, and maybe I misunderstood the whole thing) but:

Seeing people apparently argue to preserve all three U in the the 3U (unexpected undetectable undefined) behaviour undermines the glorious promises of Rust safety, and the claims of the supposed impossibilities of writing various kinds of bugs in Rust.

This very much damages the Rust cause, and that is something that also ought also to be considered along with the issue of introducing safe but breaking changes for existing users, because new users are coming to Rust for safety, and there will be more new users than existing users. (And most existing users also came for safety).

Those who want stability over safety will stay where they are.

The over-caution about breaking changes turns these expectations on their head. How do you think users feel: Yeah, we didn't introduce the safety of this breaking change 'cos a very few of you might need to make a patch your work and recompile to fix an actual bug, so we left it unexpectedly undetectably unsafe as a favour to you.

Because that is what this looks like.

So while everyone and their dog is now compiling C with -Werror and -Wall and literally begging to get as many breaking changes as they can, and looking yet harder by paying good time and money for Coverity and Klocwork and Black Duck etc, and trying every C compiler they can get their hands on for maximum warnings, people are arguing that Rust should to cover it all up. 

Because that is what it looks like. 

I cannot comprehend the mindset behind that. I'd love to know what some of you been smoking so that I can make sure I never ingest any of it.

All the "nobody is writing such bugs" claims, are just begging for it to come up 10 years later in the post-mortem of a severe exploit, yet we just had a lengthy post-mortem discussed on Zulip because somebody wrote such bugs, and some poor chap spent a week trying to find the cause. 

And we're more worried about "breaking changes" than actual breakage? The promise of Rust was that it should have been impossible to write that bug.

And I don't think much about the idea of simply mentioning such risk in a note at the bottom of a locked filing cabinet stuck in a disused lavatory with a sign on the door saying 'Beware of the Leopard'.

It's having this sort of secretly-documented unexpected undetectable undefined behaviour regularly foisted on us by new optimisations in the C language compilers (introducing new bugs and UB in old code in the process) that drive us to Rust in the first place. That's the sort of breaking change we don't like. We really want safety. If we didn't we wouldn't be spending millions and billions across the board between us on rewriting and retooling for Rust.

It honestly looks like I'm watching TLA-sponsored exploits being embedded into Rust. I can't account for it in any other way.

I just say that this is what it looks like, and it is very damaging to the image of Rust, precisely because it could be very damaging to compiled code, and given a useful combination of gadgets, also damaging to the systems using them, and those using the systems.

I'm sure this isn't the only case, but I daren't look. I'm trying to make a case for Rust based on its promises of safety, and it is a real conflict to know that I might undercover an apparent conspiracy to not only keep the failures of such guarantees hidden, but even to maintain those failures as failures!

I beg in the name of transparency and accountability, that whatever rules need changing are changed, so that awareness of the failure of Rust safety guarantees is paramount:

  • There is a specific public list of any bug or flaw which could accidentally permit unexpected undetectable undefined or unsafe behaviour, along with a collection of instances where it has been discovered.
    There is: see: https://github.com/rust-lang/rust/labels/I-unsound
  • Clippy detection be implemented rapidly, even if there is no fix (or no agreed fix), with the clippy message containing a link to the issue, even if it is unfixable.
  • Breaking fixes must be adopted within a small fixed timescale if non-breaking fixes aren't adopted

I also suggest that if there is a strong case to retain these unsafe potentialities, it is not overriding enough to block the fix. If there is a strong case, then maybe potentially unsafe code can continue to be combined and compiled and released by those with such needs, by use of a strongly frowned upon compiler flag to deselect the safety of the breaking fixes.

But this practice of presenting the preservation of unexpected undetectable unsafe behaviours as some kind of unqualified benefit to those who are fleeing that sort of behaviour in other languages should stop. We undergo the expense and inconvenience and business risk to get guarantees of safety, and not guarantees of stability, which if we are honest, we know are better had by doing absolutely nothing.

With ever-increasing adoption, if Rust or libs are breaking the safety guarantees, the best time to fix them is yesterday, not maybe someday.