pub struct BilinearSurface<const NX: usize, const NY: usize, X: AxisLookup<NX> = BinaryAxis<NX>, Y: AxisLookup<NY> = BinaryAxis<NY>> { /* private fields */ }Expand description
A static rectilinear u16 × u16 → i32 surface.
The handle references static tables and never owns or copies them. NX is
the number of X knots and NY the number of Y knots.
§Orientation
The value grid is row-major with Y selecting the row and X selecting the
column, so a value is addressed as values[y][x]. Because the grid type is
&'static [[i32; NX]; NY], a transposed grid is a type error when NX != NY,
and there is no reachable dimension-mismatch outcome. For a square grid,
the transposed shape has the same type, so preserving orientation remains
the caller’s responsibility.
§Lookup strategies
The last two type parameters select how each axis locates a coordinate, and
they default to BinaryAxis — the general-purpose choice, and the only one
BilinearSurface::new can produce. BilinearSurface<NX, NY> is therefore
the binary-knotted surface.
A firmware that wants a different trade names it in the type and builds the
surface with BilinearSurface::from_axes: LinearAxis
for a tiny axis, UniformAxis for evenly spaced knots
that then need not be stored at all, or
BucketedAxis to buy a smaller search bound on a long
irregular axis with a few static index bytes. The two axes choose
independently, and the choice is a type rather than a value: there is no
runtime discriminant and no branch among strategies.
Choose LinearAxis for a tiny axis when the minimum
auxiliary structure is what matters; BinaryAxis as the general default;
UniformAxis when knots are evenly spaced, so the
knot arrays can be dropped and location is constant work;
BucketedAxis for a long irregular axis when
2*B extra index bytes buy a smaller local bound.
Whichever strategies a surface names, it locates the same cell, evaluates the same value, and reports the same errors. Only the stored bytes and the search work differ.
§Validation
BilinearSurface::new is a const fn that rejects fewer than two knots
on either axis and any axis that is not strictly increasing. A definition
that violates those invariants fails to compile. Every axis strategy
validates itself the same way in its own const fn constructor, so a surface
built with BilinearSurface::from_axes is validated before it exists.
§Storage
The referenced table element payload is exactly
BilinearSurface::PAYLOAD_BYTES: the two axes’
AxisLookup::KNOT_BYTES and
AxisLookup::INDEX_BYTES plus
BilinearSurface::VALUE_BYTES (4*NX*NY). For the default binary
surface that equals 2*NX + 2*NY + 4*NX*NY bytes. That figure excludes
this handle, alignment, linker effects, code, stack, and binary or flash
placement. It is not a total memory cost.
The handle always contains the value-grid reference and the four-byte
boundary policy. Its remaining fields depend on the axis strategies:
UniformAxis stores no reference, LinearAxis and BinaryAxis each
store one knot-array reference, and BucketedAxis stores a knot-array and
an index-array reference. The default binary/binary handle is therefore
three thin references plus the policy and alignment padding. Its size is
BilinearSurface::HANDLE_BYTES, which is target-dependent.
§Equality
The derived PartialEq and Hash implementations compare and hash the
referenced tables, not their addresses. Two handles over distinct but equal
tables therefore compare equal, at a cost proportional to NX * NY.
§Examples
use ph_surfaces::{BilinearSurface, Boundary, BoundaryPolicy};
static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 2]; 3] = [[0, 1], [2, 3], [4, 5]];
static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES)
.with_policy(BoundaryPolicy::new().with_y_above(Boundary::Clamp));
assert_eq!(SURFACE.nx(), 2);
assert_eq!(SURFACE.ny(), 3);
assert_eq!(SURFACE.values()[2][1], 5);
assert_eq!(SURFACE.policy().y_above(), Boundary::Clamp);
assert_eq!(SURFACE.policy().y_below(), Boundary::Error);A grid whose dimensions are swapped does not compile. Here the axes call
for [[i32; 2]; 3] but the grid is [[i32; 3]; 2]:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 3]; 2] = [[0, 1, 2], [3, 4, 5]];
static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES);The same declaration with the correct [[i32; NX]; NY] shape compiles:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 2]; 3] = [[0, 1], [2, 3], [4, 5]];
static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES);
assert_eq!(SURFACE.values()[2][1], 5);An axis whose knot count disagrees with the value grid does not compile either, because the axis type carries that count:
use ph_surfaces::{BilinearSurface, BinaryAxis};
static X: [u16; 3] = [0, 50, 100];
static Y: [u16; 2] = [0, 100];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];
static SURFACE: BilinearSurface<2, 2, BinaryAxis<3>, BinaryAxis<2>> =
BilinearSurface::from_axes(BinaryAxis::new(&X), BinaryAxis::new(&Y), &VALUES);Implementations§
Source§impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>
impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>
Sourcepub fn evaluate(&self, x: u16, y: u16) -> Result<i32, SurfaceError>
pub fn evaluate(&self, x: u16, y: u16) -> Result<i32, SurfaceError>
Evaluates this surface at (x, y) with deterministic X-then-Y bilinear
interpolation.
§Order
The composition is normative, not an implementation detail:
- interpolate along X on the lower-Y row;
- interpolate along X on the upper-Y row;
- interpolate those two already rounded results along Y.
Because every step rounds to nearest with exact half-way values away
from zero, a Y-then-X implementation would return different values, so
this order is observable. For the axes [0, 2] with rows
[[0, 0], [1, 3]], this order returns 1 at (1, 1) where Y-then-X
would return 2:
use ph_surfaces::BilinearSurface;
static AXIS: [u16; 2] = [0, 2];
static VALUES: [[i32; 2]; 2] = [[0, 0], [1, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&AXIS, &AXIS, &VALUES);
// X on the lower row: 0. X on the upper row: (1 + 3) / 2 = 2.
// Y between them: (0 + 2) / 2 = 1.
assert_eq!(SURFACE.evaluate(1, 1), Ok(1));§Domain
X is resolved before Y. If both coordinates leave the domain on sides
selecting Boundary::Error, the X-side error
is the one reported. If the X side clamps, Y is still resolved under its
own two selections, so a clamped X can be followed by a Y error.
A clamped coordinate is replaced by the nearest declared endpoint knot and then evaluated through this same path. Nothing extrapolates: the result of a clamped evaluation is a value the surface actually declares on its boundary.
use ph_surfaces::{BilinearSurface, Boundary, BoundaryPolicy};
static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 100], [200, 300]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES)
.with_policy(BoundaryPolicy::new().with_x_above(Boundary::Clamp));
// X clamps to 10 and evaluates the boundary column, never past it.
assert_eq!(SURFACE.evaluate(4_000, 0), Ok(100));
// Y still errors on its own side, even though X clamped.
assert_eq!(
SURFACE.evaluate(4_000, 11),
Err(ph_surfaces::SurfaceError::YAbove { coordinate: 11, bound: 10 }),
);§Errors
Returns the SurfaceError variant naming the side the coordinate fell
off, carrying the coordinate as supplied and the applicable first or
last knot of that axis. Only a side selecting
Boundary::Error can produce one.
§Cost
A successful evaluation performs exactly
BilinearSurface::SUCCESS_INTERPOLATIONS scalar interpolations and
exactly BilinearSurface::SUCCESS_GRID_READS reads of the value grid.
Each in-domain axis lookup costs two endpoint comparisons plus the
search work of that axis’s strategy —
ceil(log2(len)) probes for the default
BinaryAxis, and at most
AxisLookup::MAX_SEARCH_COMPARISONS
comparisons for any of them. A clamped lookup costs one or two endpoint
comparisons and performs no probes: the endpoint path, not a search. A
rejected coordinate returns before any interpolation or value-grid
read, and an X rejection also skips the Y lookup because X is resolved
first.
The value grid is never scanned and its size affects only in-domain lookup cost. Evaluation allocates nothing, keeps no state, and has no warm-up, reset, cache, or lifecycle behaviour: the same handle and the same coordinates always produce the same result.
The arithmetic cannot overflow for any surface this crate can define.
That is the bound proven for the private scalar helper: both weights are
nonnegative and sum to a span of at most 65_535, and each rounded
result stays inside the convex hull of its two endpoints. The Y step
therefore receives two i32 values drawn from the hull of the four
corner values and returns one from the same hull, so there is no
overflow outcome to report.
§Examples
use ph_surfaces::{BilinearSurface, SurfaceError};
static X: [u16; 3] = [0, 10, 30];
static Y: [u16; 2] = [0, 100];
static VALUES: [[i32; 3]; 2] = [[0, 10, 30], [100, 110, 130]];
static SURFACE: BilinearSurface<3, 2> = BilinearSurface::new(&X, &Y, &VALUES);
// A declared knot returns its stored value exactly.
assert_eq!(SURFACE.evaluate(10, 100), Ok(110));
// An interior point of the plane.
assert_eq!(SURFACE.evaluate(20, 50), Ok(70));
// Out of domain on the default Error policy.
assert_eq!(
SURFACE.evaluate(31, 0),
Err(SurfaceError::XAbove { coordinate: 31, bound: 30 }),
);Examples found in repository?
25fn main() {
26 // Declared knot: the three interpolation steps land exactly on the stored
27 // value, so it is recovered without numerical drift.
28 assert_eq!(SURFACE.evaluate(100, 10), Ok(0));
29 // Interior operating point from docs/interpolation-walkthrough.md:
30 // lower-X 25, upper-X 75, Y 50.
31 assert_eq!(SURFACE.evaluate(125, 20), Ok(50));
32 assert_eq!(
33 SURFACE.evaluate(0, 20),
34 Err(SurfaceError::XBelow {
35 coordinate: 0,
36 bound: 100
37 })
38 );
39
40 assert_eq!(BilinearSurface::<2, 2>::VALUE_BYTES, 16);
41 assert_eq!(BilinearSurface::<2, 2>::PAYLOAD_BYTES, 24);
42 assert_eq!(BilinearSurface::<2, 2>::SUCCESS_INTERPOLATIONS, 3);
43 assert_eq!(BilinearSurface::<2, 2>::SUCCESS_GRID_READS, 4);
44 assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
45 assert_eq!(
46 BilinearSurface::<2, 2>::HANDLE_BYTES,
47 core::mem::size_of::<BilinearSurface<2, 2>>()
48 );
49}More examples
28fn main() {
29 // Endpoint accessors reconstruct the arithmetic progression.
30 assert_eq!(SURFACE.x_knot(0), 0);
31 assert_eq!(SURFACE.x_knot(2), 200);
32 assert_eq!(SURFACE.y_knot(1), 50);
33
34 // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35 assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36 assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37 for x in [0u16, 50, 100, 199, 200] {
38 for y in [0u16, 25, 50, 99, 100] {
39 assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40 }
41 }
42
43 assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44 assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45 assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46 assert_eq!(Compensation::VALUE_BYTES, 36);
47 assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48 assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49 assert_eq!(
50 Compensation::HANDLE_BYTES,
51 core::mem::size_of::<Compensation>()
52 );
53}38fn main() {
39 // Nested tuning: discard indexes that do not beat Binary's bound of 5.
40 assert_eq!(max_local_comparisons(&X, &X_INDEX_2), 9); // 4 index bytes; worse
41 assert_eq!(max_local_comparisons(&X, &X_INDEX_4), 5); // 8 bytes; no improvement
42 assert_eq!(max_local_comparisons(&X, &X_INDEX_8), 3); // 16 bytes; meets bound 3
43 assert_eq!(max_local_comparisons(&X, &X_INDEX_16), 2); // 32 bytes; only if 3 is not enough
44 assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
45
46 assert_eq!(MIXED.evaluate(610, 400), DEFAULT.evaluate(610, 400));
47 assert_eq!(MIXED.evaluate(610, 400), Ok(0));
48 assert_eq!(MIXED.y_knot(8), 1_600); // described, not stored
49 for x in [0u16, 100, 610, 1_205, 1_600] {
50 for y in [0u16, 200, 800, 1_600] {
51 assert_eq!(MIXED.evaluate(x, y), DEFAULT.evaluate(x, y));
52 }
53 }
54
55 assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
56 assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
57 assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
58 assert_eq!(Mixed::PAYLOAD_BYTES, 662);
59 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
60}32fn main() {
33 assert_eq!(FAIL_SAFE.policy().x_below(), Boundary::Error);
34 assert_eq!(FAIL_SAFE.policy().x_above(), Boundary::Clamp);
35 assert_eq!(FAIL_SAFE.policy().y_below(), Boundary::Error);
36 assert_eq!(FAIL_SAFE.policy().y_above(), Boundary::Clamp);
37
38 // In-domain: policy is idle.
39 assert_eq!(FAIL_SAFE.evaluate(125, 20), Ok(50));
40 assert_eq!(FAIL_SAFE.evaluate(125, 20), STRICT.evaluate(125, 20));
41
42 // Reject uncharacterized low codes.
43 assert_eq!(
44 FAIL_SAFE.evaluate(0, 20),
45 Err(SurfaceError::XBelow {
46 coordinate: 0,
47 bound: 100
48 })
49 );
50 assert_eq!(
51 FAIL_SAFE.evaluate(125, 0),
52 Err(SurfaceError::YBelow {
53 coordinate: 0,
54 bound: 10
55 })
56 );
57
58 // Hold the last characterized high edge; nothing is extrapolated.
59 assert_eq!(FAIL_SAFE.evaluate(4_000, 20), FAIL_SAFE.evaluate(200, 20));
60 assert_eq!(FAIL_SAFE.evaluate(125, 4_000), FAIL_SAFE.evaluate(125, 30));
61 assert_eq!(FAIL_SAFE.evaluate(4_000, 20), Ok(140));
62 assert_eq!(FAIL_SAFE.evaluate(125, 4_000), Ok(75));
63
64 // Both outside Error sides: X wins and Y is not reported.
65 assert_eq!(
66 STRICT.evaluate(0, 0),
67 Err(SurfaceError::XBelow {
68 coordinate: 0,
69 bound: 100
70 })
71 );
72 // X clamps, Y still errors.
73 assert_eq!(
74 FAIL_SAFE.evaluate(4_000, 0),
75 Err(SurfaceError::YBelow {
76 coordinate: 0,
77 bound: 10
78 })
79 );
80}20fn tiny_linear_linear() {
21 // 3×2 firmware table. Linear is the "tiny axis" starting point; Binary
22 // stores the same knots and, on this shape, the same comparison bound.
23 static X: [u16; 3] = [0, 10, 20];
24 static Y: [u16; 2] = [0, 100];
25 static VALUES: [[i32; 3]; 2] = [[0, 1, 2], [10, 11, 12]];
26
27 type TinyLinear = BilinearSurface<3, 2, LinearAxis<3>, LinearAxis<2>>;
28 type TinyBinary = BilinearSurface<3, 2>;
29
30 static LINEAR: TinyLinear =
31 BilinearSurface::from_axes(LinearAxis::new(&X), LinearAxis::new(&Y), &VALUES);
32 static BINARY: TinyBinary = BilinearSurface::new(&X, &Y, &VALUES);
33
34 assert_eq!(LINEAR.evaluate(10, 100), Ok(11));
35 assert_eq!(LINEAR.evaluate(10, 100), BINARY.evaluate(10, 100));
36
37 // Referenced payload: 4*3*2 values + 2*3 + 2*2 knots = 24 + 10 = 34.
38 assert_eq!(TinyLinear::VALUE_BYTES, 24);
39 assert_eq!(TinyLinear::PAYLOAD_BYTES, 34);
40 assert_eq!(TinyBinary::PAYLOAD_BYTES, 34);
41 assert_eq!(TinyLinear::HANDLE_BYTES, core::mem::size_of::<TinyLinear>());
42
43 // Work: four successful endpoint comparisons plus (3-1)+(2-1) = 3 search
44 // comparisons = 7 knot comparisons. Binary is ceil(log2(3))+ceil(log2(2))
45 // = 2+1 = 3 search comparisons as well. Choosing Linear vs Binary on this
46 // shape requires target code/timing evidence, not a universal threshold.
47 assert_eq!(<LinearAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
48 assert_eq!(<LinearAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
49 assert_eq!(<BinaryAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
50 assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
51 assert_eq!(TinyLinear::SUCCESS_INTERPOLATIONS, 3);
52 assert_eq!(TinyLinear::SUCCESS_GRID_READS, 4);
53}
54
55fn uniform_uniform_17x9() {
56 // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57 // Location is a subtraction and a division by a compile-time STEP — zero
58 // knot comparisons, not zero cycles.
59 type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60 type AllBinary = BilinearSurface<17, 9>;
61
62 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63 static SURFACE: UniformPair =
64 BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66 assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67 assert_eq!(SURFACE.x_knot(16), 1_600);
68 assert_eq!(SURFACE.y_knot(8), 1_600);
69
70 assert_eq!(UniformPair::VALUE_BYTES, 612);
71 assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73 assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74 assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75 assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76 assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77 assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78 assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79 assert_eq!(
80 UniformPair::HANDLE_BYTES,
81 core::mem::size_of::<UniformPair>()
82 );
83}
84
85fn mixed_bucketed_uniform_17x9() {
86 static X: [u16; 17] = [
87 0, 100, 210, 300, 405, 500, 610, 700, 805, 900, 1_010, 1_100, 1_205, 1_300, 1_410, 1_500,
88 1_600,
89 ];
90 static X_INDEX: [u16; 8] = bucket_index(&X);
91 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
92
93 type Mixed = BilinearSurface<17, 9, BucketedAxis<17, 8>, UniformAxis<9, 0, 200>>;
94 type AllBinary = BilinearSurface<17, 9>;
95
96 static MIXED: Mixed =
97 BilinearSurface::from_axes(BucketedAxis::new(&X, &X_INDEX), UniformAxis::new(), &VALUES);
98
99 assert_eq!(MIXED.evaluate(1_600, 1_600), Ok(0));
100
101 // Payload: 612 grid + 34 X knots + 16 X index + 0 Y = 662. Binary is 664.
102 assert_eq!(Mixed::VALUE_BYTES, 612);
103 assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
104 assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
105 assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
106 assert_eq!(Mixed::PAYLOAD_BYTES, 662);
107 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
108 assert_eq!(max_local_comparisons(&X, &X_INDEX), 3);
109
110 // Work: four endpoint comparisons, plus at most 3 X local comparisons and
111 // 0 Y comparisons = 7 knot comparisons, versus 4 + 5 + 4 = 13 for
112 // Binary/Binary. Each Bucketed search also reads one bucket and maps the
113 // coordinate arithmetically; that is not included in the comparison count
114 // and is not a cycle count.
115 assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
116 assert_eq!(<BinaryAxis<9>>::MAX_SEARCH_COMPARISONS, 4);
117 assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
118 assert_eq!(Mixed::SUCCESS_INTERPOLATIONS, 3);
119 assert_eq!(Mixed::SUCCESS_GRID_READS, 4);
120 assert_eq!(Mixed::HANDLE_BYTES, core::mem::size_of::<Mixed>());
121}Source§impl<const NX: usize, const NY: usize> BilinearSurface<NX, NY, BinaryAxis<NX>, BinaryAxis<NY>>
impl<const NX: usize, const NY: usize> BilinearSurface<NX, NY, BinaryAxis<NX>, BinaryAxis<NY>>
Sourcepub const fn new(
x_axis: &'static [u16; NX],
y_axis: &'static [u16; NY],
values: &'static [[i32; NX]; NY],
) -> Self
pub const fn new( x_axis: &'static [u16; NX], y_axis: &'static [u16; NY], values: &'static [[i32; NX]; NY], ) -> Self
Declares a surface over static axes and a static row-major value grid, with both axes located by binary search.
This is the general-purpose constructor and the one to reach for unless
an axis has a reason to choose otherwise; see
BilinearSurface::from_axes for the surfaces that do.
Every domain side defaults to Boundary::Error;
use BilinearSurface::with_policy to select clamping on any side.
§Panics
Panics unless both axes declare at least two knots and both axes are strictly increasing. In a constant or static definition that panic is a compile error, so an invalid surface cannot be defined.
A single X knot is rejected:
use ph_surfaces::BilinearSurface;
static X: [u16; 1] = [0];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 1]; 2] = [[0], [1]];
static SURFACE: BilinearSurface<1, 2> = BilinearSurface::new(&X, &Y, &VALUES);An empty Y axis is rejected:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [0, 10];
static Y: [u16; 0] = [];
static VALUES: [[i32; 2]; 0] = [];
static SURFACE: BilinearSurface<2, 0> = BilinearSurface::new(&X, &Y, &VALUES);A duplicated X knot is rejected:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [5, 5];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);A descending X axis is rejected:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [10, 0];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);A duplicated Y knot is rejected independently of the X axis:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [5, 5];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);A descending Y axis is rejected independently of the X axis:
use ph_surfaces::BilinearSurface;
static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [10, 0];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);Examples found in repository?
More examples
Source§impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>
impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>
Sourcepub const VALUE_BYTES: usize
pub const VALUE_BYTES: usize
Bytes of the referenced value grid: NX*NY elements of i32.
Exact and target-independent. It excludes the axis tables, the handle, alignment, code, and stack, and it is not a total memory figure.
Sourcepub const PAYLOAD_BYTES: usize
pub const PAYLOAD_BYTES: usize
Bytes of referenced table elements this surface names:
X::KNOT_BYTES + X::INDEX_BYTES + Y::KNOT_BYTES + Y::INDEX_BYTES + VALUE_BYTES.
Exact and target-independent. For the default binary pairing it equals
2*NX + 2*NY + 4*NX*NY. It is only the referenced element payload: not
total RAM, flash, binary, or linker cost.
Sourcepub const HANDLE_BYTES: usize
pub const HANDLE_BYTES: usize
Size of this handle on the current target, including alignment padding.
Target-dependent: it follows pointer width and the selected strategies’
fields (Uniform stores no axis reference, Linear/Binary one, Bucketed
two), plus the value-grid reference and the four-byte policy. It does
not grow with NX or NY for a fixed pairing, and it is not a flash
or binary cost.
Sourcepub const SUCCESS_INTERPOLATIONS: u32 = 3
pub const SUCCESS_INTERPOLATIONS: u32 = 3
Scalar interpolations a successful evaluate performs.
Always three: X on the lower-Y row, X on the upper-Y row, then Y between those two already-rounded results. A rejected evaluation returns before any of them. This is operation structure, not a cycle count.
Sourcepub const SUCCESS_GRID_READS: u32 = 4
pub const SUCCESS_GRID_READS: u32 = 4
Value-grid reads a successful evaluate performs.
Always four: the corners of the located cell. A rejected evaluation returns before any of them. The grid is never scanned. This is operation structure, not a cycle count.
Sourcepub const fn from_axes(x: X, y: Y, values: &'static [[i32; NX]; NY]) -> Self
pub const fn from_axes(x: X, y: Y, values: &'static [[i32; NX]; NY]) -> Self
Declares a surface over two axes that have already chosen their lookup strategies, and a static row-major value grid.
Each axis validated itself when it was declared, so there is nothing left to reject here: the knot counts are carried in the axis types, so an axis that does not match the grid is a type error rather than a runtime one.
Every domain side defaults to Boundary::Error;
use BilinearSurface::with_policy to select clamping on any side.
§Examples
A surface whose X axis is evenly spaced — so its knots are described rather than stored — and whose Y axis keeps the default strategy:
use ph_surfaces::{BilinearSurface, BinaryAxis, UniformAxis};
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 5]; 2] = [[0, 25, 50, 75, 100], [10, 35, 60, 85, 110]];
static SURFACE: BilinearSurface<5, 2, UniformAxis<5, 0, 25>, BinaryAxis<2>> =
BilinearSurface::from_axes(UniformAxis::new(), BinaryAxis::new(&Y), &VALUES);
assert_eq!(SURFACE.evaluate(50, 0), Ok(50));
assert_eq!(SURFACE.x_knot(1), 25);Examples found in repository?
More examples
30 static LINEAR: TinyLinear =
31 BilinearSurface::from_axes(LinearAxis::new(&X), LinearAxis::new(&Y), &VALUES);
32 static BINARY: TinyBinary = BilinearSurface::new(&X, &Y, &VALUES);
33
34 assert_eq!(LINEAR.evaluate(10, 100), Ok(11));
35 assert_eq!(LINEAR.evaluate(10, 100), BINARY.evaluate(10, 100));
36
37 // Referenced payload: 4*3*2 values + 2*3 + 2*2 knots = 24 + 10 = 34.
38 assert_eq!(TinyLinear::VALUE_BYTES, 24);
39 assert_eq!(TinyLinear::PAYLOAD_BYTES, 34);
40 assert_eq!(TinyBinary::PAYLOAD_BYTES, 34);
41 assert_eq!(TinyLinear::HANDLE_BYTES, core::mem::size_of::<TinyLinear>());
42
43 // Work: four successful endpoint comparisons plus (3-1)+(2-1) = 3 search
44 // comparisons = 7 knot comparisons. Binary is ceil(log2(3))+ceil(log2(2))
45 // = 2+1 = 3 search comparisons as well. Choosing Linear vs Binary on this
46 // shape requires target code/timing evidence, not a universal threshold.
47 assert_eq!(<LinearAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
48 assert_eq!(<LinearAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
49 assert_eq!(<BinaryAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
50 assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
51 assert_eq!(TinyLinear::SUCCESS_INTERPOLATIONS, 3);
52 assert_eq!(TinyLinear::SUCCESS_GRID_READS, 4);
53}
54
55fn uniform_uniform_17x9() {
56 // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57 // Location is a subtraction and a division by a compile-time STEP — zero
58 // knot comparisons, not zero cycles.
59 type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60 type AllBinary = BilinearSurface<17, 9>;
61
62 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63 static SURFACE: UniformPair =
64 BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66 assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67 assert_eq!(SURFACE.x_knot(16), 1_600);
68 assert_eq!(SURFACE.y_knot(8), 1_600);
69
70 assert_eq!(UniformPair::VALUE_BYTES, 612);
71 assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73 assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74 assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75 assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76 assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77 assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78 assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79 assert_eq!(
80 UniformPair::HANDLE_BYTES,
81 core::mem::size_of::<UniformPair>()
82 );
83}
84
85fn mixed_bucketed_uniform_17x9() {
86 static X: [u16; 17] = [
87 0, 100, 210, 300, 405, 500, 610, 700, 805, 900, 1_010, 1_100, 1_205, 1_300, 1_410, 1_500,
88 1_600,
89 ];
90 static X_INDEX: [u16; 8] = bucket_index(&X);
91 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
92
93 type Mixed = BilinearSurface<17, 9, BucketedAxis<17, 8>, UniformAxis<9, 0, 200>>;
94 type AllBinary = BilinearSurface<17, 9>;
95
96 static MIXED: Mixed =
97 BilinearSurface::from_axes(BucketedAxis::new(&X, &X_INDEX), UniformAxis::new(), &VALUES);Sourcepub const fn with_policy(self, policy: BoundaryPolicy) -> Self
pub const fn with_policy(self, policy: BoundaryPolicy) -> Self
Returns this surface with its boundary policy replaced.
The referenced tables are unchanged; only the four domain-side selections differ.
Sourcepub const fn x(&self) -> &X
pub const fn x(&self) -> &X
Returns the X axis together with its lookup strategy.
This is the generic route to the axis: through it, code bounded on
AxisLookup (or KnotArray for the stored
strategies) can read the domain bounds, individual knots, and cost
constants of any surface’s axis without carrying the knot arrays
separately. The strategy-specific x_knot / x_min / x_max
accessors remain the constant-context conveniences.
Sourcepub const fn y(&self) -> &Y
pub const fn y(&self) -> &Y
Returns the Y axis together with its lookup strategy.
The Y counterpart of x; see there.
Sourcepub const fn values(&self) -> &'static [[i32; NX]; NY]
pub const fn values(&self) -> &'static [[i32; NX]; NY]
Returns the declared row-major value grid, addressed as values[y][x].
Sourcepub const fn policy(&self) -> BoundaryPolicy
pub const fn policy(&self) -> BoundaryPolicy
Returns the four domain-side selections.
Examples found in repository?
32fn main() {
33 assert_eq!(FAIL_SAFE.policy().x_below(), Boundary::Error);
34 assert_eq!(FAIL_SAFE.policy().x_above(), Boundary::Clamp);
35 assert_eq!(FAIL_SAFE.policy().y_below(), Boundary::Error);
36 assert_eq!(FAIL_SAFE.policy().y_above(), Boundary::Clamp);
37
38 // In-domain: policy is idle.
39 assert_eq!(FAIL_SAFE.evaluate(125, 20), Ok(50));
40 assert_eq!(FAIL_SAFE.evaluate(125, 20), STRICT.evaluate(125, 20));
41
42 // Reject uncharacterized low codes.
43 assert_eq!(
44 FAIL_SAFE.evaluate(0, 20),
45 Err(SurfaceError::XBelow {
46 coordinate: 0,
47 bound: 100
48 })
49 );
50 assert_eq!(
51 FAIL_SAFE.evaluate(125, 0),
52 Err(SurfaceError::YBelow {
53 coordinate: 0,
54 bound: 10
55 })
56 );
57
58 // Hold the last characterized high edge; nothing is extrapolated.
59 assert_eq!(FAIL_SAFE.evaluate(4_000, 20), FAIL_SAFE.evaluate(200, 20));
60 assert_eq!(FAIL_SAFE.evaluate(125, 4_000), FAIL_SAFE.evaluate(125, 30));
61 assert_eq!(FAIL_SAFE.evaluate(4_000, 20), Ok(140));
62 assert_eq!(FAIL_SAFE.evaluate(125, 4_000), Ok(75));
63
64 // Both outside Error sides: X wins and Y is not reported.
65 assert_eq!(
66 STRICT.evaluate(0, 0),
67 Err(SurfaceError::XBelow {
68 coordinate: 0,
69 bound: 100
70 })
71 );
72 // X clamps, Y still errors.
73 assert_eq!(
74 FAIL_SAFE.evaluate(4_000, 0),
75 Err(SurfaceError::YBelow {
76 coordinate: 0,
77 bound: 10
78 })
79 );
80}Source§impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BinaryAxis<NX>, Y>
impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BinaryAxis<NX>, Y>
Source§impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, LinearAxis<NX>, Y>
impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, LinearAxis<NX>, Y>
Source§impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BinaryAxis<NY>>
impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BinaryAxis<NY>>
Source§impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, LinearAxis<NY>>
impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, LinearAxis<NY>>
Source§impl<const NX: usize, const NY: usize, const B: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BucketedAxis<NX, B>, Y>
impl<const NX: usize, const NY: usize, const B: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BucketedAxis<NX, B>, Y>
Source§impl<const NX: usize, const NY: usize, const B: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BucketedAxis<NY, B>>
impl<const NX: usize, const NY: usize, const B: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BucketedAxis<NY, B>>
Source§impl<const NX: usize, const NY: usize, const ORIGIN: u16, const STEP: u16, Y: AxisLookup<NY>> BilinearSurface<NX, NY, UniformAxis<NX, ORIGIN, STEP>, Y>
impl<const NX: usize, const NY: usize, const ORIGIN: u16, const STEP: u16, Y: AxisLookup<NY>> BilinearSurface<NX, NY, UniformAxis<NX, ORIGIN, STEP>, Y>
Sourcepub const fn x_knot(&self, index: usize) -> u16
pub const fn x_knot(&self, index: usize) -> u16
Examples found in repository?
28fn main() {
29 // Endpoint accessors reconstruct the arithmetic progression.
30 assert_eq!(SURFACE.x_knot(0), 0);
31 assert_eq!(SURFACE.x_knot(2), 200);
32 assert_eq!(SURFACE.y_knot(1), 50);
33
34 // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35 assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36 assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37 for x in [0u16, 50, 100, 199, 200] {
38 for y in [0u16, 25, 50, 99, 100] {
39 assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40 }
41 }
42
43 assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44 assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45 assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46 assert_eq!(Compensation::VALUE_BYTES, 36);
47 assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48 assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49 assert_eq!(
50 Compensation::HANDLE_BYTES,
51 core::mem::size_of::<Compensation>()
52 );
53}More examples
55fn uniform_uniform_17x9() {
56 // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57 // Location is a subtraction and a division by a compile-time STEP — zero
58 // knot comparisons, not zero cycles.
59 type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60 type AllBinary = BilinearSurface<17, 9>;
61
62 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63 static SURFACE: UniformPair =
64 BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66 assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67 assert_eq!(SURFACE.x_knot(16), 1_600);
68 assert_eq!(SURFACE.y_knot(8), 1_600);
69
70 assert_eq!(UniformPair::VALUE_BYTES, 612);
71 assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73 assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74 assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75 assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76 assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77 assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78 assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79 assert_eq!(
80 UniformPair::HANDLE_BYTES,
81 core::mem::size_of::<UniformPair>()
82 );
83}Source§impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, const ORIGIN: u16, const STEP: u16> BilinearSurface<NX, NY, X, UniformAxis<NY, ORIGIN, STEP>>
impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, const ORIGIN: u16, const STEP: u16> BilinearSurface<NX, NY, X, UniformAxis<NY, ORIGIN, STEP>>
Sourcepub const fn y_knot(&self, index: usize) -> u16
pub const fn y_knot(&self, index: usize) -> u16
Examples found in repository?
28fn main() {
29 // Endpoint accessors reconstruct the arithmetic progression.
30 assert_eq!(SURFACE.x_knot(0), 0);
31 assert_eq!(SURFACE.x_knot(2), 200);
32 assert_eq!(SURFACE.y_knot(1), 50);
33
34 // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35 assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36 assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37 for x in [0u16, 50, 100, 199, 200] {
38 for y in [0u16, 25, 50, 99, 100] {
39 assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40 }
41 }
42
43 assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44 assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45 assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46 assert_eq!(Compensation::VALUE_BYTES, 36);
47 assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48 assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49 assert_eq!(
50 Compensation::HANDLE_BYTES,
51 core::mem::size_of::<Compensation>()
52 );
53}More examples
38fn main() {
39 // Nested tuning: discard indexes that do not beat Binary's bound of 5.
40 assert_eq!(max_local_comparisons(&X, &X_INDEX_2), 9); // 4 index bytes; worse
41 assert_eq!(max_local_comparisons(&X, &X_INDEX_4), 5); // 8 bytes; no improvement
42 assert_eq!(max_local_comparisons(&X, &X_INDEX_8), 3); // 16 bytes; meets bound 3
43 assert_eq!(max_local_comparisons(&X, &X_INDEX_16), 2); // 32 bytes; only if 3 is not enough
44 assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
45
46 assert_eq!(MIXED.evaluate(610, 400), DEFAULT.evaluate(610, 400));
47 assert_eq!(MIXED.evaluate(610, 400), Ok(0));
48 assert_eq!(MIXED.y_knot(8), 1_600); // described, not stored
49 for x in [0u16, 100, 610, 1_205, 1_600] {
50 for y in [0u16, 200, 800, 1_600] {
51 assert_eq!(MIXED.evaluate(x, y), DEFAULT.evaluate(x, y));
52 }
53 }
54
55 assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
56 assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
57 assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
58 assert_eq!(Mixed::PAYLOAD_BYTES, 662);
59 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
60}55fn uniform_uniform_17x9() {
56 // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57 // Location is a subtraction and a division by a compile-time STEP — zero
58 // knot comparisons, not zero cycles.
59 type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60 type AllBinary = BilinearSurface<17, 9>;
61
62 static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63 static SURFACE: UniformPair =
64 BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66 assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67 assert_eq!(SURFACE.x_knot(16), 1_600);
68 assert_eq!(SURFACE.y_knot(8), 1_600);
69
70 assert_eq!(UniformPair::VALUE_BYTES, 612);
71 assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72 assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73 assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74 assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75 assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76 assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77 assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78 assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79 assert_eq!(
80 UniformPair::HANDLE_BYTES,
81 core::mem::size_of::<UniformPair>()
82 );
83}Trait Implementations§
Source§impl<const NX: usize, const NY: usize, X: Clone + AxisLookup<NX>, Y: Clone + AxisLookup<NY>> Clone for BilinearSurface<NX, NY, X, Y>
impl<const NX: usize, const NY: usize, X: Clone + AxisLookup<NX>, Y: Clone + AxisLookup<NY>> Clone for BilinearSurface<NX, NY, X, Y>
Source§fn clone(&self) -> BilinearSurface<NX, NY, X, Y>
fn clone(&self) -> BilinearSurface<NX, NY, X, Y>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more