<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://trillium.rs/blog</id>
    <title>trillium Blog</title>
    <updated>2026-04-02T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://trillium.rs/blog"/>
    <subtitle>trillium Blog</subtitle>
    <entry>
        <title type="html"><![CDATA[On Semver]]></title>
        <id>https://trillium.rs/blog/2026/04/02/on-semver</id>
        <link href="https://trillium.rs/blog/2026/04/02/on-semver"/>
        <updated>2026-04-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Along with the Trillium 1.0 release it seems worth discussing what 1.0 means in rust and in this project, as well as publishing a set of clear project-specific expectations going forward. TL;DR: 1.0 is just the first major release, but hopefully not the last.]]></summary>
        <content type="html"><![CDATA[<p>Along with the Trillium 1.0 release it seems worth discussing what 1.0 means in rust and in this project, as well as publishing a set of clear project-specific expectations going forward. TL;DR: 1.0 is just the first major release, but hopefully not the last.</p>
<p>If you look at your Cargo.toml files, you'll find quite a few rust projects that have been around for many years and are on 0.y.z and have mature release cadences and practices. Meanwhile, there are crates that treat 1.0 like an indefinite promise of stability, and it is a true rarity to find crates at a version greater than 2 or 3.</p>
<p>What is the cost of this? A whole range of expressive capacity --- when a package is 0.x, there is no notion of distinguishing "semver-compatible, but introduces new features" from "a tiny bug fix or documentation correction." Our communication channel is limited to a boolean "did it break or not." Post-1.0 crates get the full semver triple of compatibility communication: A breakage signal as well as a feature-vs-bugfix signal. Additionally, it artificially communicates software immaturity far beyond what is reasonable.</p>
<p>Here's what the <a href="https://semver.org/" target="_blank" rel="noopener noreferrer" class="">semver specification</a> says about 0.y.z vs 1.y.z:</p>
<blockquote>
<ol start="4">
<li class="">Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.</li>
</ol>
</blockquote>
<blockquote>
<ol start="5">
<li class="">Version 1.0.0 defines the public API. The way in which the version number is incremented after this release is dependent on this public API and how it changes.</li>
</ol>
</blockquote>
<p>and</p>
<blockquote>
<ol start="8">
<li class="">Major version X (X.y.z | X &gt; 0) MUST be incremented if any backward incompatible changes are introduced to the public API. It MAY also include minor and patch level changes. Patch and minor versions MUST be reset to 0 when major version is incremented.</li>
</ol>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-means-for-trillium">What this means for trillium<a href="https://trillium.rs/blog/2026/04/02/on-semver#what-this-means-for-trillium" class="hash-link" aria-label="Direct link to What this means for trillium" title="Direct link to What this means for trillium" translate="no">​</a></h2>
<p>In light of the above, trillium is going 1.0 with the current release instead of 0.3, but with slightly different intentions than the rust community's proclivity to stay at 0 or 1 indefinitely.</p>
<p>In brief, breaking changes (semver-major releases) will:</p>
<ul>
<li class="">happen</li>
<li class="">be infrequent</li>
<li class="">have a clearly communicated upgrade path in release notes</li>
<li class="">be preceded by a semver-minor deprecation where possible</li>
<li class="">be preceded by a prerelease window</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-releases-will-happen">Major releases <em>will</em> happen<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-releases-will-happen" class="hash-link" aria-label="Direct link to major-releases-will-happen" title="Direct link to major-releases-will-happen" translate="no">​</a></h3>
<p>Semver-major breaking changes are a normal and healthy part of even mature software evolution. The major version that comes after 1 is 2, and 3 comes after that, etc. 1.0 doesn't mean frozen forever, just that releases will be carefully managed and will avoid churn. Any major change is like an edition that you can choose to take if and when you want.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-releases-will-happen-infrequently">Major releases will happen infrequently<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-releases-will-happen-infrequently" class="hash-link" aria-label="Direct link to Major releases will happen infrequently" title="Direct link to Major releases will happen infrequently" translate="no">​</a></h3>
<p>Nobody wants churn from their dependencies. Semver-major releases will happen often enough to facilitate software evolution and improvement, but not so frequently that it feels like a burden without commensurate value to stay on the latest version. Major release frequency will be determined by new functionality offered by the rust language, the introduction of new protocols that do not fit into the current interface shapes without breaking changes, or more generally a limitation in expressive capacity of the current interfaces and types.</p>
<p>I'm not committing to a specific release cadence aside from setting an upper bound on frequency: Breaking changes will not happen more frequently than twice a year. The four year interval from 0.2 to the next breaking release of 1.0 accumulated far too many breaking changes. I'd be quite happy if the interval between 1.0 and 2.0 were more like one year.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-releases-will-have-a-clear-upgrade-path">Major releases will have a clear upgrade path<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-releases-will-have-a-clear-upgrade-path" class="hash-link" aria-label="Direct link to Major releases will have a clear upgrade path" title="Direct link to Major releases will have a clear upgrade path" translate="no">​</a></h3>
<p>Every major release will include a changelog with documentation of every breaking change and what will need to change in your code. Although I can't promise this, I intend to offer tooling to automate codemod-able upgrade paths wherever possible.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-releases-will-be-preceded-by-deprecation-notices">Major releases will be preceded by deprecation notices<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-releases-will-be-preceded-by-deprecation-notices" class="hash-link" aria-label="Direct link to Major releases will be preceded by deprecation notices" title="Direct link to Major releases will be preceded by deprecation notices" translate="no">​</a></h3>
<p>At least two weeks prior to a breaking change, a minor release will annotate any removals with <code>#[deprecated]</code>, to the extent that the rust language allows.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-releases-will-be-preceded-by-a-prerelease-with-an-open-window-for-feedback">Major releases will be preceded by a prerelease, with an open window for feedback<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-releases-will-be-preceded-by-a-prerelease-with-an-open-window-for-feedback" class="hash-link" aria-label="Direct link to Major releases will be preceded by a prerelease, with an open window for feedback" title="Direct link to Major releases will be preceded by a prerelease, with an open window for feedback" translate="no">​</a></h3>
<p>At least two weeks prior to a breaking change, a prerelease will be published to crates.io, providing a window for users to ensure that their applications can smoothly upgrade and provide feedback if not. Prereleases will be accompanied by the upgrade documentation as described above.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="major-versions-will-receive-security-updates">Major versions will receive security updates<a href="https://trillium.rs/blog/2026/04/02/on-semver#major-versions-will-receive-security-updates" class="hash-link" aria-label="Direct link to Major versions will receive security updates" title="Direct link to Major versions will receive security updates" translate="no">​</a></h3>
<p>For some as-yet determined period of time (at least a year) after the release of the next major version, previous versions will get dependency updates and security patches that do not require breaking changes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reexport-semantics-for-trillium">Reexport semantics for trillium<a href="https://trillium.rs/blog/2026/04/02/on-semver#reexport-semantics-for-trillium" class="hash-link" aria-label="Direct link to Reexport semantics for trillium" title="Direct link to Reexport semantics for trillium" translate="no">​</a></h2>
<p>I'd like to take a slightly different interpretation of reexports than the ecosystem generally does. Every trillium crate will reexport every external type that it includes in its interfaces. If a trillium crate accepts a <code>url::Url</code>, it will <em>always</em> reexport <code>Url</code>. Trillium's semver guarantee covers its reexported types. A breaking change in an upstream crate only becomes a trillium breaking change if it affects a type trillium publicly reexports. Please use reexported types for stability. If you add a direct dependency on a transitive dependency of a trillium crate, it is your responsibility to pin versions appropriately.</p>
<p>For example, <code>trillium</code> has a dependency on <code>trillium-http</code>, the lower-level http implementation. <code>trillium</code> reexports a number of types from <code>trillium-http</code>, such as <code>Headers</code>, <code>Status</code>, <code>Version</code>, <code>Method</code>, etc. If <code>trillium-http</code> requires a breaking change to a type that trillium itself does not publicly export, that will not entail a breaking change to trillium.</p>
<p>This reexport policy is unconventional. I'm establishing it because it aligns with how I think most rust users think about API boundaries, and I'll adjust based on what we learn in practice. If you're a trillium user and this causes problems, I want to hear about it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://trillium.rs/blog/2026/04/02/on-semver#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Trillium 1.0 means a commitment to a mature release cycle and fully expressive use of semver as defined at <a href="https://semver.org/" target="_blank" rel="noopener noreferrer" class="">semver.org</a>, including in ways that slightly diverge from the rust community conventions. I encourage other maintainers who read this to reconsider their own versioning strategy if they maintain a crate that has been 0.x or 1.x for longer than a few years.</p>]]></content>
        <author>
            <name>Jacob Rothstein</name>
            <uri>https://github.com/jbr</uri>
        </author>
        <category label="meta" term="meta"/>
        <category label="policy" term="policy"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Announcing Trillium 1.0]]></title>
        <id>https://trillium.rs/blog/2026/03/30/trillium-1-0</id>
        <link href="https://trillium.rs/blog/2026/03/30/trillium-1-0"/>
        <updated>2026-03-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Trillium 1.0 is a major release across the trillium toolkit. The headline feature is HTTP/3, but the]]></summary>
        <content type="html"><![CDATA[<p>Trillium 1.0 is a major release across the trillium toolkit. The headline feature is HTTP/3, but the
release also includes substantial improvements to server lifecycle management, ergonomics, and the
client.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="http3">HTTP/3<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#http3" class="hash-link" aria-label="Direct link to HTTP/3" title="Direct link to HTTP/3" translate="no">​</a></h2>
<p>Trillium now supports HTTP/3 via the new <a href="https://docs.rs/trillium-quinn" target="_blank" rel="noopener noreferrer" class=""><code>trillium-quinn</code></a> crate,
which wraps the <a href="https://docs.rs/quinn" target="_blank" rel="noopener noreferrer" class="">Quinn</a> QUIC implementation. Consistent with trillium's
architecture, alternative async QUIC implementations would be supported if they existed, and
composes neatly with any trillium runtime adapter.</p>
<p>Existing trillium applications can add H3 support with just a line or two of code and no other
modifications.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-side-http3">Server-side HTTP/3<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#server-side-http3" class="hash-link" aria-label="Direct link to Server-side HTTP/3" title="Direct link to Server-side HTTP/3" translate="no">​</a></h3>
<p>Enable HTTP/3 alongside your existing TLS configuration:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token namespace" style="opacity:0.7">trillium_tokio</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token function" style="color:#40916c">config</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">with_acceptor</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token class-name" style="color:#1b4332">RustlsAcceptor</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">from_single_cert</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token plain">cert_pem</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"> </span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token plain">key_pem</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">with_quic</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token namespace" style="opacity:0.7">trillium_quinn</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">QuicConfig</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">from_single_cert</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token plain">cert_pem</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"> </span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token plain">key_pem</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">run</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain">handler</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><br></span></code></pre></div></div>
<p>The server binds a UDP socket on the same host and port that the tcp listener are bound to and
begins accepting QUIC connections alongside TCP. No changes to your application are otherwise
required. An alt-svc header will be added to every response to advertise that h3 is supported.</p>
<p>As of 1.0, trillium does not support dynamic QPACK tables, but they will be released as a
non-breaking minor release in the near term.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="client-side-http3">Client-side HTTP/3<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#client-side-http3" class="hash-link" aria-label="Direct link to Client-side HTTP/3" title="Direct link to Client-side HTTP/3" translate="no">​</a></h3>
<p>The Trillium client now supports HTTP/3 with automatic protocol negotiation:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token keyword" style="color:#607060;font-style:italic">use</span><span class="token plain"> </span><span class="token namespace" style="opacity:0.7">trillium_client</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">Client</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">use</span><span class="token plain"> </span><span class="token namespace" style="opacity:0.7">trillium_rustls</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">RustlsConfig</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">use</span><span class="token plain"> </span><span class="token namespace" style="opacity:0.7">trillium_rustls</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token namespace" style="opacity:0.7">rustls</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token namespace" style="opacity:0.7">client</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">ClientConfig</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">use</span><span class="token plain"> </span><span class="token namespace" style="opacity:0.7">trillium_quinn</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">ClientQuicConfig</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> client </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Client</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">new_with_quic</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token class-name" style="color:#1b4332">RustlsConfig</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token operator" style="color:#5a3e1e">&lt;</span><span class="token class-name" style="color:#1b4332">ClientConfig</span><span class="token operator" style="color:#5a3e1e">&gt;</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">default</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token class-name" style="color:#1b4332">ClientQuicConfig</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">with_webpki_roots</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><br></span></code></pre></div></div>
<p>The client tracks <code>Alt-Svc</code> response headers and automatically uses HTTP/3 for subsequent requests
to origins that advertise it. QUIC connections are pooled separately. If an H3 attempt fails, that
endpoint is marked broken and requests transparently fall back to HTTP/1.1 for a backoff period
before retrying. Origins without a cached alt-svc entry always use HTTP/1.1. As of this release,
trillium-client does not consult SVCB records or follow a "happy eyes" algorithm like browsers do,
but both of those are on the roadmap.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trailers">Trailers<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#trailers" class="hash-link" aria-label="Direct link to Trailers" title="Direct link to Trailers" translate="no">​</a></h2>
<p>As part of the H3 implementation, trillium now supports http trailers, and for completeness,
chunked-body trailer support is now added to HTTP/1.1 as well. trillium client and trillium servers
each support trailers in both directions. To generate a streaming Body that dynamically determines
trailer content while it streams, use implement BodySource and use Body::new_with_trailers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-lifecycle">Server lifecycle<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#server-lifecycle" class="hash-link" aria-label="Direct link to Server lifecycle" title="Direct link to Server lifecycle" translate="no">​</a></h2>
<p>Trillium's server lifecycle has several usability improvements and extension points.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shared-server-level-state">Shared server-level state<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#shared-server-level-state" class="hash-link" aria-label="Direct link to Shared server-level state" title="Direct link to Shared server-level state" translate="no">​</a></h3>
<p>There is now a shared state map that travels through the handler stack as it initializes. This
allows handlers to communicate with each other and initialize themselves with information about the
bound listener, as well as to establish state that is available immutably throughout the
application.</p>
<p>Handler::init lifecycle methods can now add arbitrary state to the Info, and that state is available
for the lifetime of the server. The <code>Init</code> handler also has been updated to support adding or
retrieving ad-hoc state from a shared state TypeSet there:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token class-name" style="color:#1b4332">Init</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">new</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token closure-params closure-punctuation punctuation" style="color:#7a6b55">|</span><span class="token closure-params keyword" style="color:#607060;font-style:italic">mut</span><span class="token closure-params"> info</span><span class="token closure-params closure-punctuation punctuation" style="color:#7a6b55">|</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">move</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> db </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">MyDb</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token function" style="color:#40916c">connect</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token string" style="color:#7a5c2e">"db://..."</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token keyword" style="color:#607060;font-style:italic">await</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">unwrap</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    info</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">with_state</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain">db</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token punctuation" style="color:#7a6b55">}</span><span class="token punctuation" style="color:#7a6b55">)</span><br></span></code></pre></div></div>
<p>Any downstream handler can then read it:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token operator" style="color:#5a3e1e">|</span><span class="token plain">conn</span><span class="token punctuation" style="color:#7a6b55">:</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Conn</span><span class="token operator" style="color:#5a3e1e">|</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">move</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Some</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain">db</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"> </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">shared_state</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token operator" style="color:#5a3e1e">&lt;</span><span class="token class-name" style="color:#1b4332">MyDb</span><span class="token operator" style="color:#5a3e1e">&gt;</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">        </span><span class="token keyword" style="color:#607060;font-style:italic">return</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">with_status</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token class-name" style="color:#1b4332">Status</span><span class="token punctuation" style="color:#7a6b55">::</span><span class="token class-name" style="color:#1b4332">InternalServerError</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token punctuation" style="color:#7a6b55">}</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> result </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">query</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token string" style="color:#7a5c2e">"select ..."</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token keyword" style="color:#607060;font-style:italic">await</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    conn</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">ok</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token punctuation" style="color:#7a6b55">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverhandle-and-boundinfo"><code>ServerHandle</code> and <code>BoundInfo</code><a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#serverhandle-and-boundinfo" class="hash-link" aria-label="Direct link to serverhandle-and-boundinfo" title="Direct link to serverhandle-and-boundinfo" translate="no">​</a></h3>
<p><code>Config::spawn(handler)</code> now returns a <code>ServerHandle</code> that is cheaply <code>Clone</code> and covers the full
server lifecycle:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> handle </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> </span><span class="token namespace" style="opacity:0.7">trillium_tokio</span><span class="token namespace punctuation" style="opacity:0.7;color:#7a6b55">::</span><span class="token function" style="color:#40916c">config</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">port</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token number" style="color:#8b5e3c">0</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">spawn</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token plain">handler</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token comment" style="color:#9a8060;font-style:italic">// Wait for the server to finish binding, then get the bound address.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">let</span><span class="token plain"> info </span><span class="token operator" style="color:#5a3e1e">=</span><span class="token plain"> handle</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">info</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token keyword" style="color:#607060;font-style:italic">await</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token macro property" style="color:#40916c">println!</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token string" style="color:#7a5c2e">"listening on {}"</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"> info</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">url</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">handle</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">clone</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token keyword" style="color:#607060;font-style:italic">await</span><span class="token punctuation" style="color:#7a6b55">;</span><span class="token plain"> </span><span class="token comment" style="color:#9a8060;font-style:italic">// wait for the server to shut down</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token comment" style="color:#9a8060;font-style:italic">// elsewhere: shut it down and wait for all connections to drain.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">handle</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token function" style="color:#40916c">shut_down</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token punctuation" style="color:#7a6b55">.</span><span class="token keyword" style="color:#607060;font-style:italic">await</span><span class="token punctuation" style="color:#7a6b55">;</span><br></span></code></pre></div></div>
<p><code>handle.info().await</code> returns a <code>BoundInfo</code> — an immutable snapshot of the server's address and
shared state after initialization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="graceful-shutdown-swansong-replaces-stopper">Graceful shutdown: Swansong replaces Stopper<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#graceful-shutdown-swansong-replaces-stopper" class="hash-link" aria-label="Direct link to Graceful shutdown: Swansong replaces Stopper" title="Direct link to Graceful shutdown: Swansong replaces Stopper" translate="no">​</a></h2>
<p><a href="https://docs.rs/swansong" target="_blank" rel="noopener noreferrer" class=""><code>Swansong</code></a> replaces the <a href="https://docs.rs/stopper" target="_blank" rel="noopener noreferrer" class=""><code>Stopper</code></a> crate for
coordinating graceful shutdown throughout the trillium crates. Swansong provides affordances for not
only initiating a shutdown but also waiting for all shutdown guards to drop before signaling to the
caller that the shutdown is complete.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-more-async_trait-on-handlers">No more <code>#[async_trait]</code> on handlers<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#no-more-async_trait-on-handlers" class="hash-link" aria-label="Direct link to no-more-async_trait-on-handlers" title="Direct link to no-more-async_trait-on-handlers" translate="no">​</a></h2>
<p><code>impl Handler</code> no longer requires <code>#[async_trait]</code>. Remove the attribute from all handler
implementation.</p>
<p>The same applies to <code>FromConn</code> and <code>TryFromConn</code> in <code>trillium-api</code>, <code>ChannelHandler</code> in
<code>trillium-channels</code>, and <code>WebsocketHandler</code> and <code>JsonWebSocketHandler</code> in trillium-websockets.</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token comment" style="color:#9a8060;font-style:italic">// Before</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token attribute attr-name" style="color:#2d6a4f">#[async_trait]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">impl</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Handler</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">for</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">MyHandler</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token keyword" style="color:#607060;font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:#40916c">run</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token keyword" style="color:#607060;font-style:italic">self</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#7a6b55">:</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Conn</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">-&gt;</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Conn</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token punctuation" style="color:#7a6b55">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token comment" style="color:#9a8060;font-style:italic">// After</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token keyword" style="color:#607060;font-style:italic">impl</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Handler</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">for</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">MyHandler</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    </span><span class="token keyword" style="color:#607060;font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:#607060;font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:#40916c">run</span><span class="token punctuation" style="color:#7a6b55">(</span><span class="token operator" style="color:#5a3e1e">&amp;</span><span class="token keyword" style="color:#607060;font-style:italic">self</span><span class="token punctuation" style="color:#7a6b55">,</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#7a6b55">:</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Conn</span><span class="token punctuation" style="color:#7a6b55">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">-&gt;</span><span class="token plain"> </span><span class="token class-name" style="color:#1b4332">Conn</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">{</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#7a6b55">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain"></span><span class="token punctuation" style="color:#7a6b55">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conn-api-cleanup"><code>Conn</code> API cleanup<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#conn-api-cleanup" class="hash-link" aria-label="Direct link to conn-api-cleanup" title="Direct link to conn-api-cleanup" translate="no">​</a></h2>
<p>Several long-standing rough edges are resolved in 1.0.</p>
<p><strong>Header access is now unambiguous.</strong> <code>conn.headers()</code> and <code>conn.headers_mut()</code> are now split into
<code>conn.request_headers()</code> / <code>conn.request_headers_mut()</code> and <code>conn.response_headers()</code> /
<code>conn.response_headers_mut()</code>. Similarly, <code>conn.with_header(name, val)</code> is now
<code>conn.with_response_header(name, val)</code>.</p>
<p><strong><code>conn.inner()</code> is gone.</strong> Methods that were previously only reachable through <code>conn.inner()</code> are
now directly on <code>Conn</code>.</p>
<p><strong><code>set_state</code> → <code>insert_state</code>.</strong> The <code>set_state</code> method was deprecated in 0.2.20; it's removed in
1.0.</p>
<p><strong><code>conn.state_entry::&lt;T&gt;()</code></strong> provides an entry API for per-connection state, mirroring
<code>HashMap::entry</code>. See
<a href="https://docs.rs/type-set/latest/type_set/entry/enum.Entry.html" target="_blank" rel="noopener noreferrer" class="">type_set::entry::Entry</a>.</p>
<p><strong><code>set_*</code> setters now chain.</strong> Methods like <code>conn.set_status(200)</code> and <code>conn.set_body("ok")</code> now
return <code>&amp;mut Self</code>, allowing them to be chained like <code>conn.set_status(200).set_body("ok");</code> Thanks
to @joshtriplett for this suggestion!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="client-improvements">Client improvements<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#client-improvements" class="hash-link" aria-label="Direct link to Client improvements" title="Direct link to Client improvements" translate="no">​</a></h2>
<p>Beyond HTTP/3, the client gains several independent improvements.</p>
<p><strong>HTTP/1.1 keepalive is now the default.</strong> <code>Client::with_default_pool()</code> no longer exists because
pooling is always on. To opt <em>out,</em> use <code>Client::without_keepalive()</code>.</p>
<p><strong>Connection timeouts</strong> are now supported on both clients and individual requests:
<code>client.with_timeout(Duration)</code> sets a default for all requests; <code>conn.with_timeout(Duration)</code>
overrides for a single request. Both return <code>Error::TimedOut</code> on expiry.</p>
<p><strong>Per-connection state</strong> via <code>TypeSet</code>: client conns now support <code>with_state</code>, <code>insert_state</code>,
<code>state</code>, <code>state_mut</code>, and <code>take_state</code>. This is an incremental step towards the possibility of
supporting "client handlers."</p>
<p><strong><code>sonic-rs</code> support</strong> is available as an opt-in feature alternative to <code>serde_json</code> for
<code>with_json_body</code> and <code>response_json</code>. Enable with <code>features = ["sonic-rs"]</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trillium-api"><code>trillium-api</code><a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#trillium-api" class="hash-link" aria-label="Direct link to trillium-api" title="Direct link to trillium-api" translate="no">​</a></h2>
<p>Trillium-api gains substantial documentation. The primary difference from the previous versions is
that all cargo features are opt-in now (no default features). This way, you can choose between
serde_json and sonic-rs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="logger-improvements">Logger improvements<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#logger-improvements" class="hash-link" aria-label="Direct link to Logger improvements" title="Direct link to Logger improvements" translate="no">​</a></h2>
<p><code>trillium-logger</code> now places a <code>LogTarget</code> in shared state, allows any handler in the pipeline to
emit messages to the logger's configured target.</p>
<p>The <code>dev_formatter</code> output now includes the HTTP version as the first field, so you can see at a
glance whether a connection came in over HTTP/1.1 or HTTP/3.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-improvements">Testing improvements<a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#testing-improvements" class="hash-link" aria-label="Direct link to Testing improvements" title="Direct link to Testing improvements" translate="no">​</a></h2>
<p>trillium-testing introduces TestServer, which is a new testing approach for trillium
applications. The previous version is still supported in the first release in order to help make
upgrading to trillium 1.0 smooth and avoid changing both your tests and your application at the same
time. Macro-style TestConn assertions are deprecated and will be removed in the next breaking
release to trillium-testing.</p>
<p>The new approach looks like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#2a1a0a;--prism-background-color:#f0e8d8"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#2a1a0a;background-color:#f0e8d8"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#2a1a0a"><span class="token plain">use test_harness::test;</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">use trillium::{Conn, Status};</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">use trillium_testing::{TestResult, TestServer, harness};</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">#[test(harness)]</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">async fn basic_test() {</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    let app = TestServer::new(|conn: Conn| async move { conn.ok("hello") }).await;</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    app.get("/").await.assert_ok().assert_body("hello");</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    // or if you prefer:</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    let conn = app.post("/").with_body("body").await;</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    conn.assert_ok();</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    conn.assert_body("hello");</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">}</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">// also an option, but not preferred:</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">#[test]</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">fn sync_test() {</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    let app = TestServer::new_blocking(|conn: Conn| async move { conn.ok("hello") });</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    app.get("/").block().assert_ok().assert_body("hello");</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    let conn = app.post("/").with_body("body").block();</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    conn.assert_ok();</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">    conn.assert_body("hello");</span><br></span><span class="token-line" style="color:#2a1a0a"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="typeset"><code>TypeSet</code><a href="https://trillium.rs/blog/2026/03/30/trillium-1-0#typeset" class="hash-link" aria-label="Direct link to typeset" title="Direct link to typeset" translate="no">​</a></h2>
<p>The <code>StateSet</code> type has been renamed to <code>TypeSet</code> and extracted to the standalone
<a href="https://docs.rs/type-set" target="_blank" rel="noopener noreferrer" class=""><code>type-set</code></a> crate, re-exported as <code>trillium_http::TypeSet</code> and
<code>trillium::TypeSet</code>. If you were referring to <code>StateSet</code> directly, rename to <code>TypeSet</code>.</p>]]></content>
        <author>
            <name>Jacob Rothstein</name>
            <uri>https://github.com/jbr</uri>
        </author>
        <category label="release" term="release"/>
        <category label="http3" term="http3"/>
    </entry>
</feed>