Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set correctly a generic collection type in rust

Tags:

generics

rust

I am trying to test some language features in Rust 1.39 under FreeBSD 12 in comparison to Free Pascal 3.0.4 for a simple generic collection of points with 2D Points addressed by string keys. Unfortunately the code for the generic type declaration does not compile in a very early state and stops with:

error[E0106]: missing lifetime specifier
  --> src/main.rs:11:31
   |
11 |     type TPointMap = BTreeMap<&TString, TPoint>;
   |     

How do I have to rewrite the Rust code?

Details:

To test the language behavior I've written two small programs in Rust and Pascal addressing "syntactically" the same context. The Pascal program is a straightforward declaration of:

  1. some plain types, a record type and a generic map container,
  2. that will be used aftermath to define a point, store the point in a new allocated map,
  3. search the point by the key and write the data to the STDIO
  4. and finally free the map.
program test;
uses fgl; { Use the free pascal generics library }
type
   TDouble   = Double; { Define a 64 bit float } 
   TString   = String; { Define a string type  } 
   TPoint    = record  { Define a point record }
                  X : TDouble; { Coordinate X }
                  Y : TDouble; { Coordinate Y }
               end; 
   { Define a map of points with strings as key }
   TPointMap = specialize TFPGMap<TString, TPoint>;

{ Test program } 
var
   map   : TPointMap; { Declare the map variable }
   point : TPoint;    { Declare a point variable }
   found : TPoint;    { Varaiable for a found point }
   key   : TString;   { Variable to address the point in the map } 
begin
   map := TPointMap.create; { Allocate a new ma container }
   with point do begin      { Set the point variable }
      x := 1.0; y := 2.0;
   end;
   key := '123';              { Set the key address to '123'  }
   map.add(key,point);        { Store the point in the map }

   { Search the point an write the result in the rusty way }
   case map.TryGetData(key, found)  of
     true  : writeln('X: ',found.X:2;, ' Y:', found.Y:2:2);
     false : writeln('Key ''',key,''' not found');
   end;
   map.free;  { De-allocate the map }
   { Plain types are de-allocated by scope }
end.   

The program compiles and gives me:

$ ./main 
X: 1.00 Y:2.00

Here is my incorrect Rust version of the code:

use std::collections::BTreeMap; // Use a map from the collection 

type TDouble = f64; // Define the 64 bit float type
type TString = str; // Define the string type
struct TPoint {     // Define the string type
    x: TDouble,     // Coordinate X
    y: TDouble,     // Coordinate Y
}

// Define a map of points with strings as key 
type TPointMap = BTreeMap<&TString, TPoint>;

// Test program
fn main() {
    let point = TPoint { x: 1.0, y: 2.0 }; // Declare and define the point variable
    let mut map = TPointMap::new();        // Declare the map and allocate it
    let key: TString = "123";              // Declare and define the address of point 
    map.insert(&key, point);               // Add the point to the map 
    // search the point and print it
    match map.get(&key) {
        Some(found) => println!("X: {} Y: {}", found.X, found.y),
        None => println!("Key '{}' not found", key),
    }
   // map is de-allocated by scope
}

Remark: I'm aware due to the borrowing and ownership concept, that some code lines cannot be used in the Rust code. The line

 match map.get(&key)...

is one of them.

like image 223
huckfinn Avatar asked Oct 28 '25 05:10

huckfinn


1 Answers

To be the rough equivalent of the freepascal version, TString should probably be String rather than str. A freepascal String is (depending on some flags) a pointer, a length and a heap-allocated array of characters. That's (pretty much) exactly what String is. str is just the array of characters and is unsized so it always has to be behind some kind of (fat) pointer.

Once that change is made, there are only a few other things to fix the code. TPointMap needs a lifetime parameter since it uses a reference type. The lifetime on the reference has to come from somewhere, so we make TPointMap generic in that lifetime.

type TPointMap<'a> = BTreeMap<&'a TString, TPoint>;

You might also consider simply using BTreeMap<TString, TPoint> if your use-case allows for it.

We need to do a bit of conversion to declare key: TString. String literals have type 'static str, but there's a simple to_string method to convert them to Strings.

let key: TString = "123".to_string(); 

Finally, there's a typo in found.X.

Some(found) => println!("X: {} Y: {}", found.x, found.y),

Altogether, we have

use std::collections::BTreeMap; // Use a map from the collection

type TDouble = f64; // Define the 64 bit float type
type TString = String; // Define the string type
struct TPoint {
    // Define the string type
    x: TDouble, // Coordinate X
    y: TDouble, // Coordinate Y
}

// Define a map of points with strings as key
type TPointMap<'a> = BTreeMap<&'a TString, TPoint>;

// Test program
fn main() {
    let point = TPoint { x: 1.0, y: 2.0 }; // Declare and define the point variable
    let mut map = TPointMap::new(); // Declare the map and allocate it
    let key: TString = "123".to_string(); // Declare and define the address of point
    map.insert(&key, point); // Add the point to the map
                             // search the point and print it
    match map.get(&key) {
        Some(found) => println!("X: {} Y: {}", found.x, found.y),
        None => println!("Key '{}' not found", key),
    }
    // map is de-allocated by scope
}

(playground)

See also What are the differences between Rust's String and str?

like image 200
SCappella Avatar answered Oct 30 '25 12:10

SCappella



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!