Collusion with rust · parsing off files

Time:2022-5-10

Off file is a plain text file, which can be used to record three-dimensional (or higher dimensional) polyhedron information. Its basic format is

OFF
Number of points, number of faces, number of sides
Point table
Surface table

for example

OFF
4 4 6
0 0 0
1 0 0
0 1 0
0 0 1
3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3

The record is a tetrahedron defined by four vertices, with the number of sides of 6. The point table records the coordinate information of the four vertices

0 0 0
1 0 0
0 1 0
0 0 1

The surface table records the information of four patches

3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3

The number in the first column of the face table indicates how many vertices each patch is composed of, and the subsequent columns are the index numbers of the point table, for example

3 0 1 3

Indicates that the patch is composed of three vertices. The index numbers of these vertices in the point table are 0, 1 and 3 respectively, corresponding to the first point, the second point and the fourth point in the point table.

Note that the number of sides of the off file header can be set to 0, which does not affect the correctness of the polyhedron structure.

Mesh structure

Before considering how to parse the off file, you need to determine the data type in memory that can be used to express the polyhedron structure, otherwise the parsing work is difficult to implement. This data type can be defined as the structure type:

struct Mesh {
    Points: VEC < VEC < f64 > >, // point table
    Faces: VEC < VEC < use > > // face table
}

The vector used to represent points and patches(Vec)Instances are on the heap, so you usually don’t have to worryMeshInstance causes stack overflow. I wanted toMeshDefined as

struct Point {
    n: Use, // dimension
    Body: box < [f64] > // coordinates
}

struct Facet {
    n: Use, // number of vertices
    Vertices: box < [use] > // vertex index
}

struct Mesh {
    Points: VEC < point >, // point table
    Faces: VEC < face > // face table
}

The above definition is logically more consistent with the polyhedron model, but usesBoxThe pointer cannot be nullPointandFacetDynamically allocate heap space. In fact, this problem has appeared in the “n-dimensional point” section of the “point”.

The following code isMeshDefinednewanddisplaymethod:

impl Mesh {
    fn new() -> Mesh {
        return Mesh {points: Vec::new(), facets: Vec::new()};
    }
    
    fn display(&self) {
        println!("OFF");
        println!("{0} {1} 0", self.points.len(), self.facets.len());
        for x in &(self.points) {
            let n = x.len();
            for i in 0 .. n {
                if i == n - 1 {
                    println!("{}", x[i]);
                } else {
                    print!("{} ", x[i]);
                }
            }
        }
        for f in &(self.facets) {
            let n = f.len();
            print!("{} ", n);
            for i in 0 .. n {
                if i == n - 1 {
                    println!("{}", f[i]);
                } else {
                    print!("{} ", f[i]);
                }
            }
        }
    }
}

Among them,&(self.points)Express rightMeshStructuralpointsThe reference of a member can also be written as&self.pointsBecause.Priority ratio of operators&High. However, why do we need to quote here?

Reference to collection type

For vectors, when usingfor ... inWhen the syntax traverses it, the vector instance needs to be passed to the iterator as a parameter. There are three types of parameter transmission:

  • Example of transmission vector;
  • Reference of vector instance;
  • Pass a variable reference to a vector instance.

Due to the error in the above codepointsyesMeshA member of a structure whose ownership will be transferredMeshWithout this member, rustc does not allow the definition of the structure to be destroyed so arbitrarily, so an error is reported, and the method of passing reference is allowed.

Another problem is that in the process of traversing vectors, there are codes to access vector elements:

for x in &(self.points) {
    let n = x.len();
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", x[i]);
        } else {
            print!("{} ", x[i]);
        }
    }
}

amongxyesVec<f64>Instance or its reference? The answer is the latter. In fact, in the process of traversing vectors, there are three ways to obtain vector elements:

  • When transferring vector instances, the ownership of vector elements is obtained;
  • Pass the reference of vector instance and get the reference of vector element;
  • Pass the variable reference of vector instance and get the variable reference of vector element.

generic paradigm

The process of iterating point set and patch set is almost the same, that is

for x in &(self.points) {
    let n = x.len();
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", x[i]);
        } else {
            print!("{} ", x[i]);
        }
    }
}
for f in &(self.facets) {
    let n = f.len();
    print!("{} ", n);
    for i in 0 .. n {
        if i == n - 1 {
            println!("{}", f[i]);
        } else {
            print!("{} ", f[i]);
        }
    }
}

Suppose there is no in the output process of the surface table`

print!("{} ", n);

The output process of point table and can be unified into a generic function:

fn display_matrix<T>(v: &Vec<T>) {
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                println!("{}", x[i]);
            } else {
                print!("{} ", x[i]);
            }
        }
    }
}

The reason why this function is nameddisplay_matrix, because a vector whose element is a vector is a matrix?

Then, you canMeshofdisplayMethod is defined as

impl Mesh {
    fn display(&self) {
        println!("OFF");
        println!("{0} {1} 0", self.points.len(), self.facets.len());
        display_matrix(&self.points);
        display_matrix(&self.facets);
    }
}

As a result, you will be severely beaten by rustc:

error[E0599]: no method named `len` found for reference `&T` in the current scope
error[E0608]: cannot index into a value of type `&T`
error[E0608]: cannot index into a value of type `&T`

The first mistake is that rustc thinksdisplay_matrixGeneric parameters forTNo,lenmethod. The last two errors are that rustc believesTCannot get value with subscript. In other words, rustc wants me to “prove”TExistinglenMethod can also be used to obtain values through subscripts. Rustc is stupid. It doesn’t know that in my example,TbyVecType existinglenMethod can also obtain the value by subscript.

Prove that all vectors have lengths

The way of proof is based on characteristics. For example, here is a complete program code that can prove for a generic functionThavelenmethod:

trait HasLength {
    fn len(&self) -> usize;
}

impl HasLength for Vec<usize> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_vec_len<T: HasLength>(v: &T) {
    println!("{}", v.len());
}

fn main() {
    let mut a: Vec<usize> = Vec::new();
    a.push(1);
    a.push(2);
    display_vec_len(&a);
}

The above code first defines a code namedHasLengthTrain, and thenVec<usize>Type implements the trait. In generic functionsdisplay_vec_lenGeneric parameters inTLimited to the implementation ofHasLengthType of trait.

If willdisplay_vec_lenFunction applied toVec<f64>What about the type? Need forVec<f64>Also realizeHasLengthTrait。 So a new question arises. Can trail also be generic? This eliminates the need for each specificVec<T>type definitionHasLengthTrail. have a try:

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_vec_len<T: HasLength>(v: &T) {
    println!("{}", v.len());
}

fn main() {
    let mut a: Vec<usize> = Vec::new();
    a.push(1);
    a.push(2);
    display_vec_len(&a);
    
    let mut b: Vec<f64> = Vec::new();
    b.push(0.1);
    b.push(0.2);
    b.push(0.3);
    display_vec_len(&b);
}

The results are in line with expectations.

Subscripting Operator

The elements that access arrays and vectors through subscripts are rust’s syntax sugar, which is defined based on the standard librarystd::ops::IndexTrail is implemented. for example

fn main() {
    let a = vec![1, 2, 3, 4];
    println!("{}", a[2]);
}

Equivalent to

use std::ops::Index;
fn main() {
    let a = vec![1,2,3,4];
    println!("{}", a.index(2));
}

For the following generic functions

fn matrix_ Index < T > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return &v[i][j];
}

If its calling code is

let a = vec![vec![1, 2], vec![3, 4]];
println!("{}", matrix_index(&a, 0, 1));

Here are two questions. First, how to prove to rustcmatrix_indexGeneric parameters forTSupport subscript operation? secondlymatrix_indexWhat is the return type of? The answer is

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
    where <T as Index<usize>>::Output: Sized,
          <T as Index<usize>>::Output: Display {
    return &v[i][j];
}

If you want to know where the answer comes from, you need to try step by step in your mistakes.

First, try to proveTRealizedstd::ops::Index Trait:

use std::ops::Index;

fn matrix_ Index < T: index > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return &v[i][j];
}

However, trust will report an error and give rectification suggestions:

rror[E0107]: missing generics for trait `Index`
add missing generic argument
  |
  | fn matrix_index<T: Index<Idx>>(v: &Vec<T>, ... ... ...

seemIndexTrail is not easy to use. Search the rust standard library documents and findIndexTrail is defined as [1]

pub trait Index<Idx> where
    Idx: ?Sized, {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

thatVec<T>Have you implemented this trail? Achieved, the source code is

impl<T, I: SliceIndex<[T]>, A: Allocator> Index<I> for Vec<T, A> {
    type Output = I::Output;

    #[inline]
    fn index(&self, index: I) -> &Self::Output {
        Index::index(&**self, index)
    }
}

There are many grammars in the above code that I have never seen before. Combined with the suggestions given by rustc, I can only seeIndexTrail is used to prove that generic parameters support subscript operation by providing a type parameter for the trailVec<T>In terms of type, it should beSliceIndex<[T]>, the latter is a trail.

Try again:

use std::ops::Index;
use std::slice::SliceIndex;

fn matrix_ Index < T: index < sliceindex < [t] > > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return &v[i][j];
}

For the above code, rustc gives a warning:

warning: trait objects without an explicit `dyn` are deprecated
  |
  | fn matrix_index<T: Index<SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
  |                          ^^^^^^^^^^^^^^^

As advised by rustc, thematrix_indexChange to

fn matrix_ Index < T: index < dyn sliceindex < [t] > > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return &v[i][j];
}

Seeking truth from facts, I don’t know at this timedynWhat is the purpose of? Let’s listen to rustc first. After this, Amitabha, our rustc can finally say less. Finally, it just said coldly:

error[E0191]: the value of the associated type `Output` (from trait `SliceIndex`) must be specified
  |
  | ...x_index<T: Index<dyn SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
  |                         ^^^^^^^^^^^^^^^ help: specify the associated type: `SliceIndex<[T], Output = Type>`

Well, try again:

fn matrix_ Index < T: index < dyn sliceindex < [t], output = use > > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return v[i][j];
}

Now givematrix_indexThese pieces are almost like heavenly books. Even so, rustc not only doesn’t stop, but goes crazy. There are so many error messages that serious people can’t read. So I had to have a flash of inspiration. I probably guessedIndex<...>What should be filled in here. What should be filled in should be realizedSliceIndex<[T]>Type of trait. For my needs, the type of subscript should beusize, thenusizeIs it implementedSliceIndex<[T]>What about trail? The answer is yes, as evidenced by the source code of the standard library [2]. Accordingly, thematrix_indexDefined as

fn matrix_ Index < T: index < use > > (V: & VEC < T >, I: use, j: use) - > I don't know what type to return{
    return &v[i][j];
}

Now, rustc has finally stopped criticizing generic parametersTWhat’s the problem? Its attention turns tomatrix_indexThe return value type of is, and the error message is

Error [e0412]: cannot find type ` I don't know what type to return ` in this scope
    |
    |   ... Size, j: use) - > I don't know what type to return{
    |                         ^^^^^^^^^^^^^^^^^^ help: a trait with a similar name exists: `AsMut

I decided to ignore the help given by rustc because I didn’t understand what it was saying.matrix_indexIt is a generic function, and the parameter it accepts is a matrix – the elements are vectors of vectors, and the elements of the matrix are not of a specific type. What I don’t know, how can rustc know? Besides, I really can’t see itI don't know what type to returnFollowAsMutWhat are the similarities between the two names.

However, the above suggestions of rustc still give me some inspiration. The return type of a function can be trail [3]!matrix_indexWhat kind of trail should the return value be? becausematrix_indexThe return result of is passed toprintln!of Based on my shallow knowledge of rust,println!The accepted parameters must be providedDisplayImplementation of trail. Therefore, you might as well trymatrix_indexThe return value type of is set to&impl Display——RealizedDisplayA reference to the trait type, i.e

use std::ops::Index;
use std::fmt::Display;

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display {
    return &v[i][j];
}

After watching my flash of insight, rustc issued another criticism and suggestion:

error[E0277]: `<T as Index<usize>>::Output` doesn't implement `std::fmt::Display`
  |
  | ...i: usize, j: usize) -> impl Display {
  |                           ^^^^^^^^^^^^ `<T as Index<usize>>::Output` cannot be formatted with the default formatter
  ... ... ...
  | fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> impl Display 
  | where <T as Index<usize>>::Output: std::fmt::Display {
  | ++++++++++++++++++++++++++++++++++++++++++++++++++++

Give it a try:

fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
where <T as Index<usize>>::Output: Display {
    return &v[i][j];
}

Rustc also gives new error reports and suggestions:

error[E0277]: the size for values of type `<T as Index<usize>>::Output` 
cannot be known at compilation time
  |
  | fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) 
  | -> &impl Display
  |    ^^^^^^^^^^^^^ doesn't hav  a size known at compile-time
  = help: the trait `Sized` is not implemented for `<T as Index<usize>>::Output`
  ... ... ...
help: consider further restricting the associated type
  |
  | where <T as Index<usize>>::Output: Display, <T as Index<usize>>::Output: Sized {
  |                                           ++++++++++++++++++++++++++++++++++++

Therefore, there is the answer at the beginning of this section. So far, I really don’t know how to praise rustc. Perhaps it has always been a world-class problem to prove what is what. In life, please remember not to prove yourself, or your life will be as cumbersome as rust code. However, there is another possibility. What better way for rust to write the code at the beginning of this section more gracefully, but I don’t know. Now I can only hope so.

unnecessary and overelaborate formalities

Now we can write the display function of vector (matrix) which can almost unify various elements into vectors,

use std::ops::Index;
use std::fmt::Display;

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>)
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                println!("{}", x[i]);
            } else {
                print!("{} ", x[i]);
            }
        }
    }
}

impl Mesh {
    fn display(&self) {
        println!("OFF");
        println!("{0} {1} 0", self.points.len(), self.facets.len());
        display_matrix(&self.points);
        display_matrix(&self.facets);
    }
}

It feels that the amount of code is larger than that without unifying the traversal process of point table and surface table at the beginningdisplayMethods use more code, so… I just want to learn a little more about trail and generics.

Display

Now that you know something about trailMeshrealizationdisplayMethod is better thanMeshrealizationDisplayTrait。 If you follow the method in [4], you will soon find that you can’t write down becauseMeshIt is a composite structure, which needs many timeswrite!However, the implementation that can be foundDisplayAll examples are single executionwrite!。 A feasible solution is to use it firstStringType string to collect all the information to be displayed, and then execute on the stringwrite!

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0} {1} 0\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points).as_str();
        s += display_matrix(&self.facets).as_str();
        write!(f, "{}", s)
    }
}

The key point to draw here is&strandStringThe connection and difference between. These I have in rhamal Pdf [5].

closure

Please note that when trying to unify the output process of point table and surface table with generic functions, the premise is that the output process of surface table and point table is exactly the same, but in fact it is not the same. When outputting face information, not only the value of the vector in the face table (the subscript of the patch vertex in the point table), but also the number of patch vertices should be output. This difference can be achieved bydisplay_matrixEliminate by adding a parameter. for example

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               Prefix: type (to be determined) - > string
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        s += prefix(x).as_str();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

prefixIt means to add some information before each point or patch information output, but what type it should be is worth exploring, and this may be a good opportunity to learn more rust.

First, ifprefixSet toboolWhat about the type? have a try

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>, prefix: bool) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix {
            s += format!("{} ", n).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

Accordingly, theMeshofDisplayThe implementation of trait is modified to

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0} {1} 0\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, false).as_str();
        s += display_matrix(&self.facets, true).as_str();
        write!(f, "{}", s)
    }
}

The above implementation can solve the problem, but the code semantics is not good and the flexibility is too poor. Imagine if the information to be output before the output patch information is not the number of patch vertices, but bydisplay_matrixWhat other information does the user of the function decide? A better approach is to use function types asprefixType of, i.e

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: impl Fn(&T) -> String) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        s += prefix(x).as_str();
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

Then use closures asdisplay_matrixParameters ofprefixValue of:

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0} {1} 0\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, |_| "".to_string()).as_str();
        s += display_matrix(&self.facets, |x| format!("{} ", x.len())).as_str();
        write!(f, "{}", s)
    }
}

The above code can work, but it is not efficient. Because when outputting the point table,display_matrixThe function does not need to be executedprefixFunction, which now has to be executed, andprefixAn empty string is returned. When outputting a point table, how to makedisplay_matrixknowprefixIs it a null value that needs to be ignored? Just orderprefixFunction with state is enough. A feasible scheme is given below.

First, define a generic structure:

struct Prefix<T> {
    status: bool,
    body: fn(&T) -> String
}

impl<T> Prefix<T> {
    fn new() -> Prefix<T> {
        Prefix{status: false, body: |_| "".to_string()}
    }
}

Thendisplay_matrixRewrite as

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix.status {
            s += (prefix.body)(x).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

FinallyMeshofDisplayImplementation rewritten to

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0} {1} 0\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, Prefix::new()).as_str();
        s += display_matrix(&self.facets, Prefix{status: true,
                                                 body: |x| format!("{} ", x.len())}).as_str();
        write!(f, "{}", s)
    }
}

Off file – > mesh structure

After several big episodes, we can return to the subject now. The following code can open the off file,

use std::path::Path;
use std::fs::File;

let path = Path::new("foo.off");
let file = File::open(path).unwrap();

noticeunwrapIt should be thought that the type related to it may beResultorOption。 The rust standard library is always worried that the return value of a function is too direct and will lead to the destruction of the world. Therefore, it is very keen to ban the return value of a function first and then release it as appropriate.

The following code can read the first line of the off file and verify it:

use std::io::{BufRead, BufReader};

let buf = BufReader::new(file);
let mut lines_iter = buf.lines().map(|l| l.unwrap());

//Verify whether the first line of the off file is "off"
//Assert can be used for all types that implement EQ trait_ eq!  Compare for equality
assert_eq!(lines_iter.next(), Some(String::from("OFF")));

BufReaderoflinesMethod returns an iterator of typeLinesStructure.LinesThe structure providesIteratorImplementation of trait (iterator). Iterators can be used to traverse data sequences.IteratorofmapMethod can take a function as an argument and apply it to the data accessed by the iterator. In the above code,mapMethod accepts closures|l| l.unwrap()IteratorofnextMethod moves the iterator backward to point to the next element as a return value. Perform the first iteration on the iteratornextThe obtained value is the first value of the data sequence accessed by the iterator. If you continue to executenextMethod, you can access the subsequent values of the data sequence in turn, which is usually hidden infor ... inBehind grammar. If usedfor ... inSyntax, traversing each line of the file content, just

for line in buf.lines() {
    println!("{}", line.unwrap());
}

Because the second line of the off file defines the length of the point table and face table of the polyhedron structure, the process of traversing the content of the off file needs to be controlled by the counter. Therefore, in order to obtain the point table and face table conveniently, the iterator is explicitly callednextMethod comparisonfor ... inGrammar is more appropriate.

Next, parse the second line of the off file to obtain the length information of the point table and face table:

let second_line = lines_iter.next().unwrap();
let mut split = second_line.split_whitespace();
let n_of_points: usize = split.next().unwrap().parse().unwrap();
let n_of_facets: usize = split.next().unwrap().parse().unwrap();

becauseline_iterThe return value of the iterator containsStringA reference to an instance of typeunwrapAfter, you get the reference.Stringofsplit_whitespaceMethods can be based on white space character pairsStringType instance. In the last chapter, I implemented it with a simple state machinesplit_strIs aimed at&strType, and the segmentation results are saved inVec<(usize, usize)>Stringofsplit_whitespaceMethod does not return the split result of the string, but an iterator. If you use my implementationsplit_strFunction to realize the equivalent functions of the above code, just

let second_line = lines_iter.next().unwrap();
let second_line_as_str = second_line.as_str();
let split = split_str(second_line_as_str);
let n_of_points: usize = second_line_as_str[(split[0].0 .. split[0].1)].parse().unwrap();
let n_of_facets: usize = second_line_as_str[(split[1].0 .. split[1].1)].parse().unwrap();

By contrast, I still useStringofsplit_whitespaceMethod.

With the length of the point table and face table, you can use the iterator to continue reading the rest of the off file and analyze the point set information and face set information:

let mut mesh = Mesh::new();
for _i in 0 .. n_of_points {
    let line = lines_iter.next().unwrap();
    let mut p: Vec<f64> = Vec::new();
    for x in line.split_whitespace() {
        p.push(x.parse().unwrap());
    }
    mesh.points.push(p);
}
for _i in 0 .. n_of_facets {
    let line = lines_iter.next().unwrap();
    let mut f: Vec<usize> = Vec::new();
    let mut split = line.split_whitespace();
    let n:usize = split. next(). unwrap(). parse(). unwrap(); //  Ignore first element
    for x in split {
        f.push(x.parse().unwrap());
    }
    assert_eq!(n, f.len());
    mesh.facets.push(f);        
}

Note that in the abovefor ... inStatement, variableiIt is not used in the iteration process, and rustc recommends using it_iReplace. In addition, in the process of constructing the surface table, since the first number of the file content is the number of vertices of the patch, this information is only used to detect whether the number of vertices of the patch resolution result is correct.

Based on off fileMeshExample, how to verify its correctness? becauseMeshThe type has been implementedDisplayTrail, you might as well output its contents,

print!("{}", mesh);

Then compare the output content with the off file content.

Summary

use std::ops::Index;
use std::fmt::{Result, Formatter, Display};
use std::path::Path;
use std::fs::File;
use std::io::{BufRead, BufReader};

struct Mesh {
    Points: VEC < VEC < f64 > >, // point table
    Faces: VEC < VEC < use > > // face table
}

trait HasLength {
    fn len(&self) -> usize;
}

impl<T> HasLength for Vec<T> {
    fn len(&self) -> usize {
        return self.len();
    }
}

struct Prefix<T> {
    status: bool,
    body: fn(&T) -> String
}

impl<T> Prefix<T> {
    fn new() -> Prefix<T> {
        Prefix{status: false, body: |_| "".to_string()}
    }
}

fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
                                               prefix: Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
      <T as Index<usize>>::Output: Sized {
    let mut s = String::new();
    for x in v {
        let n = x.len();
        if prefix.status {
            s += (prefix.body)(x).as_str();
        }
        for i in 0 .. n {
            if i == n - 1 {
                s += format!("{}\n", x[i]).as_str();
            } else {
                s += format!("{} ", x[i]).as_str();
            }
        }
    }
    return s;
}

impl Display for Mesh {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let mut s = String::new();
        s += format!("OFF\n").as_str();
        s += format!("{0} {1} 0\n", self.points.len(), self.facets.len()).as_str();
        s += display_matrix(&self.points, Prefix::new()).as_str();
        s += display_matrix(&self.facets, Prefix{status: true,
                                                 body: |x| format!("{} ", x.len())}).as_str();
        write!(f, "{}", s)
    }
}

impl Mesh {
    fn new() -> Mesh {
        return Mesh {points: Vec::new(), facets: Vec::new()};
    }
    fn load(&mut self, path: &str) {
        let path = Path::new(path);
        let file = File::open(path).unwrap();
        let buf = BufReader::new(file);
        
        let mut lines_iter = buf.lines().map(|l| l.unwrap());
        assert_eq!(lines_iter.next(), Some(String::from("OFF")));
        let second_line = lines_iter.next().unwrap();
        let mut split = second_line.split_whitespace();
        let n_of_points: usize = split.next().unwrap().parse().unwrap();
        let n_of_facets: usize = split.next().unwrap().parse().unwrap();

        for _i in 0 .. n_of_points {
            let line = lines_iter.next().unwrap();
            let mut p: Vec<f64> = Vec::new();
            for x in line.split_whitespace() {
                p.push(x.parse().unwrap());
            }
            self.points.push(p);
        }
        for _i in 0 .. n_of_facets {
            let line = lines_iter.next().unwrap();
            let mut f: Vec<usize> = Vec::new();
            let mut split = line.split_whitespace();
            let n:usize = split. next(). unwrap(). parse(). unwrap(); //  Ignore first element
            for x in split {
                f.push(x.parse().unwrap());
            }
            assert_eq!(n, f.len());
            self.facets.push(f);        
        }
    }
}

fn main() {
    let mut mesh = Mesh::new();
    mesh.load("foo.off");
    print!("{}", mesh);
}

reference resources

[1] https://doc.rust-lang.org/std…
[2] https://doc.rust-lang.org/src…
[3] https://doc.rust-lang.org/sta…
[4] https://rustwiki.org/zh-CN/ru…
[5] https://gitee.com/garfileo/rh…