[architecture] How do I encapsulate a global mutable object efficiently

I am using the proj crate which itself is just a wrapper on the eponymous C library. It allows to convert any coordinates system into any other. Unfortunately the result of this conversion is weekly typed. Everything is a geo_types::Point, no matter the coordinate system used. In my project, I uses 3 coordinates systems: EPSG:4326 (it's gps coordinates), EPSG:2154 (used by official french elevation files), and Mercator. I would like to use strong typing to avoid mistake. To do this, I could create 3 different types: Epsg4326, Epsg2154, and Mercator. This would prevent any misuse as long as I correctly use the right type when parsing external input.

The next step would be to make it easy to convert between one coordinate system to the other by implementing the From trait for all combination (since I use only 3 coordinates system, this means that I need 6 possible conversion which is totally manageable).

This is where things start to be complicated. The convert function is a method of the Proj struct which isn't Send. Creating a Proj instance is really expensive, so I can't do this otherwise the performance would be horrible:

impl From<Mercator> for Epsg4326 {
    fn from(p: Mercator) -> Epsg4326 {
        // creating the Proj is way to expensive, don't do it at each convertion
        let convert = Proj::new_known_crs("merc", "EPSG:4326", None).unwrap();

        let (lat, lon) = convert
            .convert(Point::new(p.lon, p.lat)) // normalized order, longitude first
            .unwrap()
            .x_y();
        Epsg3226(lat, lon)
    }
}

My first idea was to use lazy_static to store the Proj object in a static variable, but Proj isn't Send. Is there a way to store this Proj instance somewhere that wouldn't hurt multi-thread performances, while being globally accessible? Maybe something like a thread-local singleton?

If it's not possible, this means that I would not be able to use the From trait, and would need to use a conversion function that would take a Proj instance as parameter, which is not as nice.

If you’re ok with having an instance per thread, the thread_local! macro will let you do this. Even though it looks like a static declaration, it can hold non-Send types.

2 Likes

I think I am, but I would like to double check. If I understand correctly, and even if I use async await, I will have no contention on the Proj instance at any time. The only downside is that I will use N times the amount of memory, with N the number of thread. Did I get that right?

Right. Any time you access the thread-local, you’ll get the current thread’s copy. If your async scheduler is moving the task around to different threads, then you might end up interacting with several different copies over the course of a single async block.

1 Like