ClientConfig and CallOptions are simultaneously #[non_exhaustive] and have pub fields. This combination is the worst of both worlds for a downstream crate.
A user reading the rustdoc sees pub base_uri: Uri, pub protocol: Protocol, ..., reaches for the obvious construction, and finds neither form compiles outside the defining crate:
// E0639: cannot create non-exhaustive structs using struct expression
let config = ClientConfig { base_uri: uri, protocol: Protocol::Connect, ..Default::default() };
// also rejected — struct update syntax is forbidden too
let config = ClientConfig { base_uri: uri, ..ClientConfig::default() };
The only legal path is the builder methods (ClientConfig::new(uri).protocol(...).default_timeout(...)), but the pub fields suggest otherwise, and the compiler error doesn't point at the builder.
#[non_exhaustive] is the right call (we will add fields), but it should be paired with pub(crate) fields and read accessors so the rustdoc and the construction story agree:
#[non_exhaustive]
pub struct ClientConfig {
pub(crate) base_uri: Uri,
pub(crate) protocol: Protocol,
// ...
}
impl ClientConfig {
pub fn new(base_uri: Uri) -> Self { ... }
pub fn protocol(mut self, p: Protocol) -> Self { ... } // existing builder methods
pub fn base_uri(&self) -> &Uri { ... } // new read accessors
pub fn protocol_ref(&self) -> Protocol { ... }
}
Same change for CallOptions.
While here, also unify the invalid-header policy: Response::with_header panics on bad input while ClientConfig::default_header and CallOptions::with_header silently drop it. A user who passes a malformed dynamic value to default_header gets no error, no log, and no header — a hard-to-debug production gap. Both sides should either panic (acceptable for compile-time-constant header names) or return Result (Response already has try_with_header to copy from).
Scope
Small, but a SemVer-major change for anyone reading the pub fields directly (which is currently allowed). Best landed alongside the RequestContext evolution decision.
ClientConfigandCallOptionsare simultaneously#[non_exhaustive]and havepubfields. This combination is the worst of both worlds for a downstream crate.A user reading the rustdoc sees
pub base_uri: Uri, pub protocol: Protocol, ..., reaches for the obvious construction, and finds neither form compiles outside the defining crate:The only legal path is the builder methods (
ClientConfig::new(uri).protocol(...).default_timeout(...)), but thepubfields suggest otherwise, and the compiler error doesn't point at the builder.#[non_exhaustive]is the right call (we will add fields), but it should be paired withpub(crate)fields and read accessors so the rustdoc and the construction story agree:Same change for
CallOptions.While here, also unify the invalid-header policy:
Response::with_headerpanics on bad input whileClientConfig::default_headerandCallOptions::with_headersilently drop it. A user who passes a malformed dynamic value todefault_headergets no error, no log, and no header — a hard-to-debug production gap. Both sides should either panic (acceptable for compile-time-constant header names) or returnResult(Responsealready hastry_with_headerto copy from).Scope
Small, but a SemVer-major change for anyone reading the
pubfields directly (which is currently allowed). Best landed alongside theRequestContextevolution decision.