Skip to content
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

fix: lifetime of geometry referenced by PreparedGeometry (version 1) #175

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2286,8 +2286,7 @@ impl$(<$lt>)? Geom for $ty_name$(<$lt>)? {
})
}

#[allow(clippy::needless_lifetimes)]
fn to_prepared_geom<'c>(&'c self) -> GResult<PreparedGeometry> {
fn to_prepared_geom<'c>(&'c self) -> GResult<PreparedGeometry<'c>> {
PreparedGeometry::new(self)
}

Expand Down Expand Up @@ -2615,6 +2614,10 @@ impl Geometry {
})
}

pub fn to_prepared_geom_owning(self) -> GResult<PreparedGeometry<'static>> {
PreparedGeometry::new_owning(self)
}

pub(crate) unsafe fn new_from_raw(
ptr: *mut GEOSGeometry,
context: &ContextHandle,
Expand Down
58 changes: 47 additions & 11 deletions src/prepared_geometry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::marker::PhantomData;

use crate::context_handle::{with_context, PtrWrap};
use crate::error::{Error, PredicateType};
use crate::functions::*;
use crate::{functions::*, Geometry};
use crate::{AsRaw, ContextHandle, GResult, Geom};
use geos_sys::*;

Expand All @@ -21,11 +23,13 @@ use geos_sys::*;
///
/// assert_eq!(prepared_geom.contains(&geom2), Ok(true));
/// ```
pub struct PreparedGeometry {
pub struct PreparedGeometry<'a> {
ptr: PtrWrap<*const GEOSPreparedGeometry>,
geom: Option<Geometry>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this field used for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's so that PreparedGeometry can optionally own the related Geometry (and have optionally static lifetime). It either has static lifetime and owns the geometry, or has limited lifetime tied to external Geometry (in which case the geom field is None).

phantom: PhantomData<&'a ()>,
}

impl PreparedGeometry {
impl<'a> PreparedGeometry<'a> {
/// Creates a new `PreparedGeometry` from a [`Geometry`](crate::Geometry).
///
/// # Example
Expand All @@ -37,18 +41,28 @@ impl PreparedGeometry {
/// .expect("Invalid geometry");
/// let prepared_geom = PreparedGeometry::new(&geom1);
/// ```
pub fn new<G: Geom>(g: &G) -> GResult<PreparedGeometry> {
pub fn new<G: Geom>(g: &'a G) -> GResult<PreparedGeometry<'a>> {
with_context(|ctx| unsafe {
let ptr = GEOSPrepare_r(ctx.as_raw(), g.as_raw());
PreparedGeometry::new_from_raw(ptr, ctx, "new")
})
}

pub fn new_owning(g: Geometry) -> GResult<PreparedGeometry<'static>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of this but I suppose it's for the case where someone wants to return both a Geometry and a PreparedGeometry right? If so I don't think it's a case that we should allow. There are many ways to handle it and I don't think returning both types from a function is a good Rust use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's for case when someone wants to be able to move PreparedGeometry freely around, encapsulate in inside some struct etc. I think it's valid case, not something that should be difficult or impossible to do. I have posted some examples in the issue. I don't see the other ways to handle it (or perhaps the answer is to not encapsulate PreparedGeometry anywhere, but that sound very limiting.)

with_context(|ctx| unsafe {
let ptr = GEOSPrepare_r(ctx.as_raw(), g.as_raw());
PreparedGeometry::new_from_raw(ptr, ctx, "new").map(|mut prep| {
prep.geom = Some(g);
prep
})
})
}

pub(crate) unsafe fn new_from_raw(
ptr: *const GEOSPreparedGeometry,
context: &ContextHandle,
caller: &str,
) -> GResult<PreparedGeometry> {
) -> GResult<PreparedGeometry<'a>> {
if ptr.is_null() {
let extra = if let Some(x) = context.get_last_error() {
format!("\nLast error: {x}")
Expand All @@ -59,7 +73,11 @@ impl PreparedGeometry {
"PreparedGeometry::{caller}{extra}",
)));
}
Ok(PreparedGeometry { ptr: PtrWrap(ptr) })
Ok(PreparedGeometry {
ptr: PtrWrap(ptr),
geom: None,
phantom: PhantomData,
})
}

/// Returns `true` if no points of the other geometry is outside the exterior of `self`.
Expand Down Expand Up @@ -346,16 +364,16 @@ impl PreparedGeometry {
}
}

unsafe impl Send for PreparedGeometry {}
unsafe impl Sync for PreparedGeometry {}
unsafe impl<'a> Send for PreparedGeometry<'a> {}
unsafe impl<'a> Sync for PreparedGeometry<'a> {}

impl Drop for PreparedGeometry {
impl<'a> Drop for PreparedGeometry<'a> {
fn drop(&mut self) {
with_context(|ctx| unsafe { GEOSPreparedGeom_destroy_r(ctx.as_raw(), self.as_raw()) });
}
}

impl AsRaw for PreparedGeometry {
impl<'a> AsRaw for PreparedGeometry<'a> {
type RawType = GEOSPreparedGeometry;

fn as_raw(&self) -> *const Self::RawType {
Expand All @@ -370,7 +388,7 @@ impl AsRaw for PreparedGeometry {
///
/// pub struct Boo {
/// #[allow(dead_code)]
/// geom: Geometry<'static>,
/// geom: Geometry,
/// pub prep: PreparedGeometry<'static>,
/// }
/// let boo = {
Expand Down Expand Up @@ -406,5 +424,23 @@ impl AsRaw for PreparedGeometry {
///
/// assert!(boo.prep.contains(&pt).unwrap());
/// ```
///
/// ```
/// use geos::{Geom, Geometry, PreparedGeometry};
///
/// let prep = {
/// let geom = Geometry::new_from_wkt("POLYGON((0 0, 10 0, 10 6, 0 6, 0 0))")
/// .expect("Invalid geometry");
/// let prep = geom
/// .to_prepared_geom_owning()
/// .expect("failed to create prepared geom");
///
/// prep
/// };
///
/// let pt = Geometry::new_from_wkt("POINT (2.5 2.5)").expect("Invalid geometry");
///
/// assert!(prep.contains(&pt).unwrap());
/// ```
#[cfg(doctest)]
pub mod lifetime_checks {}
Loading