-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ACP: ref access to core::net::Ip*Addr
octet bytes, impl AsRef<[u8]> for core::net::Ip*Addr
#535
Comments
We discussed this in today's @rust-lang/libs-api meeting. We had a variety of opinions. The common consensus was:
|
As a note, this could also be solved with |
Adds `const` `Ip*Addr::as_octets` methods providing reference access to `Ip*Addr` octets contents. See rust-lang/libs-team#535 for accepted ACP with a more detailed justification.
Adds `const` `Ip*Addr::as_octets` methods providing reference access to `Ip*Addr` octets contents. See rust-lang/libs-team#535 for accepted ACP with a more detailed justification.
libcore/net: `IpAddr::as_octets()` [ACP](rust-lang/libs-team#535) [Tracking issue](rust-lang#137259) Adds `const` `core::net::IpAddr{,v4,v6}::as_octets()` methods to provide reference access to IP address contents. The concrete usecase for me is allowing the `IpAddr` to provide an extended lifetime in contexts that want a `&[u8]`: ```rust trait AddrSlice { fn addr_slice(&self) -> &[u8]; } impl AddrSlice for IpAddrV4 { fn addr_slice(&self) -> &[u8] { // self.octets() doesn't help us here, because we can't return a reference to the owned array. // Instead we want the IpAddrV4 to continue owning the memory: self.as_octets() } } ``` (Notably, in this case we can't parameterize `AddrSlice` by a `const N: usize` (such that `fn addr_slice(&self) -> [u8; N]`) and maintain object-safety.)
Rollup merge of rust-lang#136609 - mammothbane:master, r=scottmcm libcore/net: `IpAddr::as_octets()` [ACP](rust-lang/libs-team#535) [Tracking issue](rust-lang#137259) Adds `const` `core::net::IpAddr{,v4,v6}::as_octets()` methods to provide reference access to IP address contents. The concrete usecase for me is allowing the `IpAddr` to provide an extended lifetime in contexts that want a `&[u8]`: ```rust trait AddrSlice { fn addr_slice(&self) -> &[u8]; } impl AddrSlice for IpAddrV4 { fn addr_slice(&self) -> &[u8] { // self.octets() doesn't help us here, because we can't return a reference to the owned array. // Instead we want the IpAddrV4 to continue owning the memory: self.as_octets() } } ``` (Notably, in this case we can't parameterize `AddrSlice` by a `const N: usize` (such that `fn addr_slice(&self) -> [u8; N]`) and maintain object-safety.)
Adds `const` `Ip*Addr::as_octets` methods providing reference access to `Ip*Addr` octets contents. See rust-lang/libs-team#535 for accepted ACP with a more detailed justification.
Adds `const` `Ip*Addr::as_octets` methods providing reference access to `Ip*Addr` octets contents. See rust-lang/libs-team#535 for accepted ACP with a more detailed justification.
Proposal
Problem statement
IpAddr
,Ipv4Addr
, andIpv6Addr
don't provide reference access to their contents, while they do provide an array copy via.octets()
. This is a problem in situations where we want theIp*Addr
to own and support a lifetime for the address octets as a slice —&[u8]
is very common in function arguments, as isAsRef<[u8]>
, for instance.Current
Ip*Addr
types can only support a signature likefn(&[u8])
by first stack-allocating a local via.octets()
:This is workable, if awkward, in situations where the lifetime of the array is functionally equivalent to the lifetime of the
Ip*Addr
. However, in other cases, the lack of ref access is a problem:We could always carry around a copy of the contents if we know we need a reference:
Motivating examples or use cases
This code is messy/WIP, but see
DynAddress
here. This is part of a userspace packet switch experiment that can operate at L2 or L3, and I define and providetrait DynAddress
for IPv4, IPv6, Ethernet, and IEEE802.15.4 addresses. The addresses are treated as opaque bytestrings to make the switch implementation agnostic to the address representation.Relevant code subset pulled out for readability:
Notably, I can't associate a
const LEN: usize
such thatfn as_bytes(&self) -> [u8; LEN]
because this would makeDynAddress
not object-safe. I also can't boundDynAddress
byHash
e.g. (which I need, and which&[u8]
provides) because it's not object-safe.My code (this line specifically) used to work with
smoltcp
's independentIpv4Addr
andIpv6Addr
definitions, but whensmoltcp
switched to the core::net types, my code stopped working, because this removed theas_bytes
method. I can't restore the functionality now becauseIp*Addr
doesn't provide ref access to the contents.My patch solution is to add an associated type to
DynAddress
:type BytesRepr<'a>: AsRef<[u8]> = &[u8] where Self: 'a;
, which is overridden with[u8; 4]
and[u8; 16]
for theIp*Addr
types.Solution sketch
Implement (using the same byte order as
.octets()
):pub const fn Ipv4Addr::as_array(&self) -> &[u8; 4]
pub const fn Ipv6Addr::as_array(&self) -> &[u8; 16]
pub const fn IpAddr::as_slice(&self) -> &[u8]
impl AsRef<[u8]> for Ipv4Addr
impl AsRef<[u8]> for Ipv6Addr
impl AsRef<[u8]> for IpAddr
Example from PR (linked below) for visibility:
I've opened a PR with an initial implementation.
Alternatives
Deal with it
This clearly isn't such a huge problem that people are coming out of the woodwork to demand a change, so this ACP could be rejected. As my example shows, it's possible to work around this with traits, e.g.
AsRef<[u8]>
orBorrow<Borrowed=[u8]>
.One possible reason for this rejection would be to leave the internals of the
Ip*Addr
types flexible. Currently, I believe the internal representation of these types is totally undetermined by the API — the address could be stored in a string and parsed at access time repeatedly, for all the user knows. Stabilizing this feature would mean thatIpv4Addr
must contain (or eventually point to, I suppose) a contiguous sequence of four bytes representing the address (ditto withIpv6Addr
/ 16 bytes), such that we can construct the returned refs for the new API with a valid lifetime. I think the current internal representations are canonical, so I think it is worth doing this, but the argument bears mentioning.If this result ends up being chosen, I'd at least like to leave a comment explaining the reasoning and linking back to this ACP in the source code.
Endianness
There's an endianness problem here —
.octets()
is very clear that it represents the octet (big-endian) order of the address, so a user calling this function shouldn't be confused about this. But the namesas_slice
,as_array
, andas_ref
don't make the same specification, which could possibly lead users to expect different behavior. Without bikeshedding a specific name, an alternative like.octets_ref()
that represents this more clearly could be appropriate.AsRef
AsRef
also has a canonicity problem -- there can only be oneAsRef<[u8]>
for each type, and so using octet order is an opinionated choice. ForIpv4Addr
, I think providingAsRef<[u8]>
is correct, as octet order is pragmatically speaking canonical (u32
interpretations of IPv4s are little more than a historical curiosity now), but forIpv6Addr
this is less clear. Indeed, segment order is the most common interpretation, so providingAsRef<[u8]>
could be ambiguous.On the other hand, the
Ipv6Addr
type as currently implemented cannot provide anAsRef<[u8]>
in any other order than big-endian, and if we providedas_array
, that would be fixed by the API (or require internal duplicate storage in the other endianness), so this is some argument for providingAsRef
.as_slice()
vs.as_array()
My initial implementation in the PR below provides
as_slice() -> &[u8]
instead ofas_array() -> [u8; N]
— @scottmcm mentioned that this throws away information, but still provides the ref access, so I'm planning on changing it.Links and related work
I've opened a PR implementing the functionality here. It currently provides
as_slice
rather thanas_array
, but I'm planning on updating that.AsRef<[u8]>
impls are pulled out here to avoid needing an FCP to land an initial implementation.The text was updated successfully, but these errors were encountered: