Skip to content

refactor(rust/sedona-raster): make ViewEntries the interface for passing views - #1196

Merged
james-willis merged 1 commit into
mainfrom
james/viewentries-interface
Aug 28, 2026
Merged

refactor(rust/sedona-raster): make ViewEntries the interface for passing views#1196
james-willis merged 1 commit into
mainfrom
james/viewentries-interface

Conversation

@james-willis

@james-willis james-willis commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The smell

ViewEntries::new(input.view().to_vec()).compose(&ViewEntries::new(view.to_vec()))?

Two heap allocations to read two slices. It looks like a workaround because it is one.

Why it was there

ViewEntries existed as the container type, but nothing actually passed it around. BandRef::view() returned &[ViewEntry], and every struct field carrying a view held a bare slice:

before
BandRef::view() &[ViewEntry]
StartBandArgs::view Option<&[ViewEntry]>
WithViewArgs::view &[ViewEntry]
BandOverrides::view Option<&[ViewEntry]>
RasterLoadRequest::view &[ViewEntry]
RasterLoadResult::view Vec<ViewEntry>

So slices were the real currency and ViewEntries was a method bag. compose was the only operation demanding the owned type on both sides — which is why every caller had to allocate its way in.

Two supporting details:

  • BandRefImpl already stored a ViewEntries and called .as_slice() on the way out. Returning the container removes a conversion rather than adding one.
  • Both doc comments describing this API were uncompilable as written — they said source.view().compose(&next)?, but view() returned a slice, which has no compose. They described the API this PR builds.

Changes

  • BandRef::view() -> &ViewEntries
  • compose's next takes impl AsRef<[ViewEntry]>, so a ViewEntries or a bare slice both work — existing a.compose(&b) call sites are untouched
  • The six fields above all carry ViewEntries

The motivating site is now:

let effective_view = match overrides.view {
    Some(v) => self.view().compose(v)?,
    None => self.view().clone(),
};

A duplicate identity check, silently diverged

Chasing the same thread turned up rs_ensure_loaded::view_is_identity, a private copy of ViewEntries::is_identity — which had drifted from it:

empty view, source_shape = [2, 3]
view_is_identity true (short-circuits on is_empty())
ViewEntries::is_identity false (length mismatch)

An empty view is identity only for a 0-dimensional raster, which is what the method already implements — the local copy was wrong.

It was unreachable in production: RasterLoadResult::unresolved clones the request's view, the request's view is the band's view, and start_band rejects 0-dimensional bands outright. The only things exercising the divergence were loader tests passing an empty view alongside a 2-D or 3-D source_shape — a state that cannot occur. Those fixtures now pass a real identity view over their source shape, and the helper is deleted in favour of the method.

No behaviour change

Composition, validation, and persistence are untouched. The only semantic change is that the identity check now has one definition instead of two that disagreed.

sedona-raster            183 passed; 0 failed
sedona-raster-functions  256 passed; 0 failed
sedona-raster-zarr        66 passed; 0 failed

fmt clean, clippy clean (no unused imports), sedona-raster-gdal builds.

Note

#1158 is stacked on this branch — it converts BandOverrides to a trinary Override<T>, which composes cleanly with the type change here.

@james-willis
james-willis force-pushed the james/viewentries-interface branch from 1b3de73 to 5ddcfb1 Compare August 27, 2026 19:11
@github-actions
github-actions Bot requested a review from prantogg August 27, 2026 19:14
@james-willis
james-willis force-pushed the james/viewentries-interface branch from 5ddcfb1 to 7dd2c1b Compare August 27, 2026 20:32
…ing views

`ViewEntries` existed as the container type but nothing actually passed it
around: `BandRef::view()` returned `&[ViewEntry]`, and every struct field
carrying a view held a bare slice. So each operation needing the type
re-wrapped a slice, and `compose` — taking `&Self` on both sides — forced two
allocations to read two slices:

    let source_view = ViewEntries::new(self.view().to_vec());
    let effective_view = match overrides.view {
        Some(v) => source_view.compose(&ViewEntries::new(v.to_vec()))?,
        None => source_view,
    };

becomes

    let effective_view = match overrides.view {
        Some(v) => self.view().compose(v)?,
        None => self.view().clone(),
    };

Changes:

* `BandRef::view()` returns `&ViewEntries`. `BandRefImpl` already stored one
  and called `as_slice()` on the way out, so this removes a conversion.
* `compose`'s `next` takes `impl AsRef<[ViewEntry]>`, so a `ViewEntries` or a
  bare slice both work and existing call sites are unchanged.
* `StartBandArgs::view`, `WithViewArgs::view`, `BandOverrides::view`,
  `RasterLoadRequest::view` and `RasterLoadResult::view` hold `ViewEntries`.
* Delete `rs_ensure_loaded::view_is_identity`, which duplicated
  `ViewEntries::is_identity` and had silently diverged from it: the local copy
  short-circuited `view.is_empty()` to true, so an empty view counted as
  identity over *any* source shape. An empty view is only identity for a
  0-dimensional raster, which is what the method already implements. Nothing
  in production produced an empty view — `start_band` rejects 0-dimensional
  bands — so the divergence was only reachable from loader tests that passed
  one. Those now pass a real identity view over their source shape.

No behaviour change to composition, validation, or persistence — only the
type views travel in, plus the identity check now having one definition.
@james-willis
james-willis force-pushed the james/viewentries-interface branch from 7dd2c1b to 16ce9ae Compare August 27, 2026 20:53
@james-willis
james-willis requested review from jiayuasu and paleolimbot and removed request for prantogg August 27, 2026 20:55
@jiayuasu

Copy link
Copy Markdown
Member

LGTM!

@james-willis
james-willis merged commit 7bbffc3 into main Aug 28, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants