Gestion de la mémoire déclarative

(une traduction plutôt gratuite d'un énorme article émotionnel qui, dans la pratique, fait le pont entre les possibilités de C et de Rust en termes de résolution de problèmes commerciaux et de résolution de bugs liés à la gestion manuelle de la mémoire. Il devrait également être utile pour les personnes ayant une expérience dans la collecte des ordures - il existe de nombreuses différences en termes de sémantique moins qu'il n'y paraît - environ. )


À partir du moment où je me suis intéressé à Rust, cela m'a semblé une éternité. Néanmoins, je me souviens clairement d'avoir fait la connaissance de l'analyseur d'emprunt ( vérificateur d'emprunt , ci - après dénommé BC - environ Per. ), Accompagné d'un mal de tête et de désespoir. Bien sûr, je ne suis pas le seul à souffrir - il existe de nombreux articles sur Internet sur le thème de la communication avec les ogives. Cependant, je voudrais me démarquer et mettre en évidence dans cet article l'ogive du point de vue des avantages pratiques , et pas seulement un générateur de maux de tête.


De temps en temps, je trouve des opinions que dans Rust - gestion manuelle de la mémoire ( probablement juste pas automatique avec GC, alors quoi d'autre? - environ Per. ), Cependant, je ne partage absolument pas ce point de vue. La méthode utilisée dans Rust, j'appelle le terme « gestion déclarative de la mémoire ». Pourquoi donc - maintenant je vais montrer.


Règles d'inscription


Au lieu de théoriser, écrivons quelque chose d'utile.


Rencontrez Overbook - Fiction Publishing House!


Comme tout éditeur, Overbook a des règles de conception. Plus précisément, il n'y a qu'une seule règle, aussi simple que les portes - pas de virgule . Overbuk croit sincèrement que les virgules sont une conséquence de la paresse du droit d'auteur et - citation - «devraient être exterminées en tant que phénomène». Par exemple, l'expression "Elle a lu et ri" - bonne, convenable. "Elle a lu, puis a ri" - nécessite une correction.



Cela ne semble plus simple nulle part, cependant, les auteurs attrapent régulièrement Overbuk sur le non-respect pathologique de cette règle! Comme si une telle règle n'existait pas du tout, scandaleux! Nous devons tout revérifier. Manuellement. De plus, si un brouillon est demandé par l'éditeur pour être édité, l'auteur peut envoyer une version qui a été corrigée en un, mais corrompue en un autre endroit, et donc tout doit être revérifié dès le début. Les entreprises ne tolèrent pas une attitude aussi négligente à l'égard du temps de travail, et le besoin s'est fait sentir d'automatiser le processus de capture de la «paresse de l'auteur» par lui-même. Par exemple, un programme informatique. Oui oui


Robin se précipite à la rescousse


Robin est l'un des employés de la maison d'édition qui s'est porté volontaire pour aider à la rédaction du programme, car il connaissait la programmation - c'est de la chance! Il est vrai que tout le monde à l'université, y compris Robin, a appris le C et Java, et Java VM a été déraisonnablement interdit d'installer Java à partir de la maison d'édition - c'est le tour! Eh bien, que ce soit C, beaucoup a été écrit en C, le langage est sûr et vérifié. Tout se passera comme il se doit, à 100%.


#include <stdio.h>

int main() {
    printf(".");
    return 0;
}

. , Makefile:


.PHONY: all

all:
    gcc -Wall -O0 -g main.c -o main
    ./main

:



:


$ make
gcc -Wall -O0 -g main.c -o main
./main
.

. " !"


// : #include directives

struct Mistake {
    // -  :
    char *message;
};
struct CheckResult {
    //   
    char *path;

    // NULL     
    //     
    struct Mistake *mistake;
};
struct CheckResult check(char *path, char *buf) {
    struct CheckResult result;
    result.path = path;
    result.mistake = NULL;

    // TODO(Robin):  
    // TODO(Robin):  

    return result;
}

// : main()

" " — — " , 256 ".


#define BUF_SIZE 256 * 1024

int main() {
    char buf[BUF_SIZE];

    struct CheckResult result = check("sample.txt", buf);
    if (result.mistake != NULL) {
        printf("  !");
        return 1;
    }

    return 0;
}

, , , :


struct CheckResult check(char *path, char *buf) {
    struct CheckResult result;
    result.path = path;
    result.mistake = NULL;

    FILE *f = fopen(path, "r");
    size_t len = fread(buf, 1, BUF_SIZE - 1, f);
    fclose(f);
    //    -  ,   , .
    buf[len] = '\0';

    // TODO(Robin):  

    return result;
}

, *.txt, . - , , ...


, TODO — ? . :


//   malloc
#include <stdlib.h>

// : structs, etc.

struct CheckResult check(char *path, char *buf) {
    struct CheckResult result;
    result.path = path;
    result.mistake = NULL;

    FILE *f = fopen("sample.txt", "r");
    size_t len = fread(buf, 1, BUF_SIZE - 1, f);
    fclose(f);
    buf[len] = '\0';

    //    C99,      
    //  ,  ,    ,
    //   .
    for (size_t i = 0; i < len; i++) {
        if (buf[i] == ',') {
            struct Mistake *m = malloc(sizeof(Mistake));
            m->message = " !";
            result.mistake = m;
            break;
        }
    }

    return result;
}

"" ( — ..) sample.txt:


',  , !

:


$ make
gcc -Wall -O0 -g main.c -o main
./main
  !

sample2.txt — :


     .

:


$ make
gcc -Wall -O0 -g main.c -o main
./main

! .


...


:


, , . , , .
, , . ?

. ". Mistake , , .". — :


struct Mistake {
    char *message;

    //   
    char *location;
}
// ...
struct CheckResult check(char *path, char *buf) {
    // :  'result' 
    // :  

   for (size_t i = 0; i < len; i++) {
        if (buf[i] == ',') {
            struct Mistake *m = malloc(sizeof(struct Mistake));

            // :  
            m->location = &buf[i];

            m->message = " !";
            result.mistake = m;
            break;
        }
    }

    return result;
}

:


void report(struct CheckResult result) {
    printf("\n");
    printf("~ %s ~\n", result.path);
    if (result.mistake == NULL) {
        printf(" !!\n");
    } else {
        // : "%s"   .
        //       12  ,  ,
        printf(
            ": %s: '%.12s'\n",
            result.mistake->message,
            result.mistake->location
        );
    }
}

int main() {
    char buf[BUF_SIZE];
    {
        struct CheckResult result = check("sample.txt", buf);
        report(result);
    }
    {
        struct CheckResult result = check("sample2.txt", buf);
        report(result);
    }
}

«», — . :


$ make
gcc -Wall -O0 -g main.c -o main
./main

~ sample.txt ~
:  !: ',  '

~ sample2.txt ~
 !!

. , , ?


#define MAX_RESULTS 128

// : ,   

int main() {
    char buf[BUF_SIZE];

    struct CheckResult bad_results[MAX_RESULTS];
    int num_results = 0;

    char *paths[] = { "sample2.txt", "sample.txt", NULL };
    for (int i = 0; paths[i] != NULL; i++) {
        char *path = paths[i];
        struct CheckResult result = check(path, buf);
        bad_results[num_results++] = result;
    }

    for (int i = 0; i < num_results; i++) {
        report(bad_results[i]);
    }
}

. — , . .


$ make
gcc -Wall -O0 -g main.c -o main
./main

~ sample2.txt ~
 !!

~ sample.txt ~
:  !: ',  '


:


, ,
, , , ! - — . ?

"!" — — " !"
. , , :


int main() {
    // :  

    //   "sample2.txt", "sample.txt"
    char *paths[] = { "sample.txt", "sample2.txt", NULL };

    for (int i = 0; paths[i] != NULL; i++) {
        //  
    }

    for (int i = 0; i < num_results; i++) {
        report(results[i]);
    }
}
```bash
$ make
gcc -Wall -O0 -g main.c -o main
./main

~ sample.txt ~
:  !: '   '

~ sample2.txt ~
 !!

- . , , :


— , . ! , - — !


- , , , , , :


— , . . .


. — . — .


" -?" — .


//  ,  memcpy
#include <string.h>

struct CheckResult check(char *path, char *buf) {
    // : ,  

    for (size_t i = 0; i < len; i++) {
        if (buf[i] == ',') {
            struct Mistake *m = malloc(sizeof(struct Mistake));

            //   12   "m->location"
            size_t location_len = len - i;
            if (location_len > 12) {
                location_len = 12;
            }
            m->location = malloc(location_len + 1);
            memcpy(m->location, &buf[i], location_len);
            m->location[location_len] = '\0';

            m->message = " !";
            result.mistake = m;
            break;
        }
    }

    return result;
}

, , , sample3.txt:


   ,    

, !


$ make
gcc -Wall -O0 -g main.c -o main
./main

~ sample.txt ~
:  !: ',  '

~ sample2.txt ~
 !!

~ sample3.txt ~
mistake:  : ',    '


, — :


, . ,
. , , . , , , , ?

" ", . , , malloc(), free() . , ? , 100%, - , - - . , .


int main() {
    char buf[BUF_SIZE];

    struct CheckResult results[MAX_RESULTS];
    int num_results = 0;

    // : ,  

    //   !
    for (int i = 0; i < num_results; i++) {
        free(results[i].mistake);
    }
}

. — buf results . , .


— , ?
— , ?
— , . .
— , , ! . , . .
— . ?


. . , . , . :


— . free(), , .


, , - , , , " ", / , .


int main() {
    //   ...

    //      !
    for (int i = 0; i < num_results; i++) {
        free(results[i].mistake->location);
        free(results[i].mistake);
    }
}

— , . free() , , , , .


- , , . . -, , , , . :


— , free(). .


, , , , , , . . . .


— -, , , , .



. , , , . , . , , .
, .


— , . , .


, . . - . , ?


— , , ?
— . , .


. ( !) . , , . .


. , . , . .


— , — , — ?
— , 130 .


, .


#define MAX_RESULTS 128

int main() {
    char buf[BUF_SIZE];

    struct CheckResult results[MAX_RESULTS];

    // ...
}

, , , . results ( , ). , results results. buf , results , , buf , . . MAX_RESULTS 256. .


— … . . , . , - , , . . .


. 256 , , . . .


" ." — , — " Rust."



(, — ..)


cargo new checker src/main.rs. . :


use std::fs::read_to_string;

fn main() {
    let s = read_to_string("sample.txt");
}

", Ruby!" — , - . . , , Ruby. .


", ?"


fn main() {
    let s = read_to_string("sample.txt");
    s.find(",");
}

, :


error[E0599]: no method named `find` found for type `std::result::Result<std::string::String, std::io::Error>` in the current scope
 --> src\main.rs:5:7
  |
5 |     s.find(",");
  |       ^^^^

. — Result<String, Error> find() , String. .


fn main() -> Result<(), std::io::Error> {
    let s = read_to_string("sample.txt")?;
    s.find(",");

    Ok(())
}

, main() , read_to_string() main(), . , , . , find():


fn main() -> Result<(), std::io::Error> {
    let s = read_to_string("sample.txt")?;

    let result = s.find(",");
    println!("Result: {:?}", result);

    Ok(())
}

$ cargo run --quiet
Result: Some(22)

. - , Option::Some(index), , Option::None. ( , — ..):


fn main() -> Result<(), std::io::Error> {
    let path = "sample.txt";
    let s = read_to_string(path)?;

    println!("~ {} ~", path);
    match s.find(",") {
        Some(index) => {
            println!(":  :  №{}", index);
        },
        None => println!(" !!"),
    }

    Ok(())
}

$ cargo run --quiet
~ sample.txt ~
:  :  №22

, , , . .


fn main() -> Result<(), std::io::Error> {
    let path = "sample.txt";
    let s = read_to_string(path)?;

    println!("~ {} ~", path);
    match s.find(",") {
        Some(index) => {
            // ,      ,
            //    12 ,
            //     
            let slice = &s[index..];
            println!(":  : {:?}", slice);
        }
        None => println!(" !!"),
    }
    Ok(())
}

$ cargo run --quiet
~ sample.txt ~
:  : ',  , !'

. malloc()! memcpy()! ( Go , — ..)! {:?}, , , . , free() . , , — s match. (&s[index..]) , , . , , .



main() , , check():


fn main() -> Result<(), std::io::Error> {
    check("sample.txt")?;

    Ok(())
}

fn check(path: &str) -> Result<(), std::io::Error> {
    let s = read_to_string(path)?;

    println!("~ {} ~", path);
    match s.find(",") {
        Some(index) => {
            let slice = &s[index..];
            println!(":  : {:?}", slice);
        }
        None => println!(" !!"),
    }

    Ok(())
}

, . .
, , . :


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    check("sample.txt", &mut s)?;

    Ok(())
}

fn check(path: &str, s: &mut String) -> Result<(), std::io::Error> {
    use std::fs::File;
    use std::io::Read;

    s.clear();
    File::open(path)?.read_to_string(s)?;

    println!("~ {} ~", path);
    match s.find(",") {
        Some(index) => {
            let slice = &s[index..];
            println!(":  : {:?}", slice);
        }
        None => println!(" !!"),
    }

    Ok(())
}

:


  • s 256 , .
  • — , : , .
  • , #include, use , : Read read_to_string check().

, , . check() CheckResult, Mistake. Rust CheckResult , Option<Mistake>. , Mistake . :


struct Mistake {
    location: &str,
}

. .


$ cargo run --quiet
error[E0106]: missing lifetime specifier
  --> src\main.rs:10:15
   |
10 |     location: &str,
   |               ^ expected lifetime parameter

"", , " !", , . , , — (— ..). , :


struct Mistake<'a> {
    location: &'a str,
}

, , , , . , , .
, check() Option<Mistake> () (, , — ..):


fn check(path: &str, s: &mut String) -> Result<Option<Mistake>, std::io::Error> {
    use std::fs::File;
    use std::io::Read;

    s.clear();
    File::open(path)?.read_to_string(s)?;

    println!("~ {} ~", path);

    Ok(match s.find(",") {
        Some(index) => {
            let location = &s[index..];
            Some(Mistake { location })
        }
        None => None,
    })
}

, . — , . match — , Ok(match ...) . , main():


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    let result = check("sample.txt", &mut s)?;

    if let Some(mistake) = result {
        println!(":  : {:?}", mistake.location);
    }

    Ok(())
}

" , result ", , " ". - if let, , match, !
!


$ cargo run --quiet
error[E0106]: missing lifetime specifier
  --> src\main.rs:17:55
   |
17 | fn check(path: &str, s: &mut String) -> Result<Option<Mistake>, std::io::Error> {
   |                                                       ^^^^^^^ expected lifetime parameter

, . , :


: , , path s.

? , Mistake location: &'a str — . s — . :


//         :
use std::io::Error as E;

fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    // ...
}

, . 'a? . ? , ? , ? , .


error[E0261]: use of undeclared lifetime name `'a`
  --> src\main.rs:19:26
   |
19 | fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
   |                          ^^ undeclared lifetime

error[E0261]: use of undeclared lifetime name `'a`
  --> src\main.rs:19:66
   |
19 | fn check(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
   |                                                                  ^^ undeclared lifetime

. ? :


struct Mistake<'a> {
    location: &'a str,
}

, , 'a location: &'a str 'a Mistake<'a>, Java. . Java Rust?


fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    // ...
}

, !


$ cargo run --quiet
~ sample.txt ~
:  : ",  , !"

!


, :


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    let path = "sample.txt";
    let result = check(path, &mut s)?;

    println!("~ {} ~", path);
    if let Some(mistake) = result {
        println!(":  : {:?}", mistake.location);
    }

    Ok(())
}

struct Mistake<'a> {
    location: &'a str,
}

use std::io::Error as E;

fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    use std::fs::File;
    use std::io::Read;

    s.clear();
    File::open(path)?.read_to_string(s)?;

    Ok(match s.find(",") {
        Some(index) => {
            let location = &s[index..];
            Some(Mistake { location })
        }
        None => None,
    })
}

'a , , . , . report():


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    let path = "sample.txt";
    let result = check(path, &mut s)?;
    report(path, result);

    Ok(())
}

fn report(path: &str, result: Option<Mistake>) {
    println!("~ {} ~", path);
    if let Some(mistake) = result {
        println!(":  : {:?}", mistake.location);
    } else {
        println!(" !!");
    }
}

, Mistake, Display .


struct Mistake<'a> {
    // !
    path: &'static str,
    location: &'a str,
}

, , , 'static. , — . check():


fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    use std::fs::File;
    use std::io::Read;

    s.clear();
    File::open(path)?.read_to_string(s)?;

    Ok(match s.find(",") {
        Some(index) => {
            let location = &s[index..];
            // !
            Some(Mistake { path, location })
        }
        None => None,
    })
}

:


$ cargo run --quiet
error[E0621]: explicit lifetime required in the type of `path`
  --> src\main.rs:37:28
   |
27 | fn check<'a>(path: &str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
   |                    ---- help: add explicit lifetime `'static` to the type of `path`: `&'static str`
...
37 |             Some(Mistake { path, location })
   |                            ^^^^ lifetime `'static` required

// new: &'static
fn check<'a>(path: &'static str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    // ...
}

Display:


use std::fmt;

impl<'a> fmt::Display for Mistake<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}:  : {:?}",
            self.path, self.location
        )
    }
}

report():


fn report(result: Option<Mistake>) {
    if let Some(mistake) = result {
        println!("{}", mistake);
    }
}

, :)


$ cargo run --quiet
sample.txt:  : ",  , !"

50 , , . , ?



, . !


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    let paths = ["sample.txt", "sample2.txt", "sample3.txt"];
    for path in &paths {
        let result = check(path, &mut s)?;
        report(result);
    }

    Ok(())
}

$ cargo run --quiet
sample.txt:  : ",  , !"
sample3.txt:  :  ",    "

. , , . .


fn main() -> Result<(), std::io::Error> {
    let mut s = String::with_capacity(256 * 1024);

    let paths = ["sample.txt", "sample2.txt", "sample3.txt"];

    //  
    let mut results = vec![];
    for path in &paths {
        let result = check(path, &mut s)?;
        results.push(result);
    }

    //  
    for result in results {
        report(result);
    }

    Ok(())
}

...


$ cargo run --quiet
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src\main.rs:9:34
  |
9 |         let result = check(path, &mut s)?;
  |                                  ^^^^^^ mutable borrow starts here in previous iteration of loop

. -? s ??


- . , , s, &s. — , , &mut s. ? , s :


fn check<'a>(path: &'static str, s: &'a mut String) -> Result<Option<Mistake<'a>>, E> {
    //     `s` - 
    s.clear();

    //       `s` -  !
    File::open(path)?.read_to_string(s)?;

    //  .
}

, . , ...


Rust.
.
, !
, Rust !!


.
. , , . memcpy(), -.


Mistake. location , . Mistake , . Rust?


String, , &str . , , . .


struct Mistake<'a> {
    //    ,   
    path: &'static str,

    //      Mistake
    location: String,
}

, , :


error[E0392]: parameter `'a` is never used
  --> src\main.rs:27:16
   |
27 | struct Mistake<'a> {
   |                ^^ unused parameter
   |
   = help: consider removing `'a` or using a marker such as `std::marker::PhantomData`

"-, ", , <'a> . 'a Display check():


struct Mistake {
    //    ,   
    path: &'static str,

    //      Mistake
    location: String,
}
// ...
impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}:  : {:?}",
            self.path, self.location
        )
    }
}
// ...
fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
    s.clear();
    File::open(path)?.read_to_string(s)?;

    Ok(match s.find(",") {
        Some(index) => {
            let location = &s[index..];
            Some(Mistake { path, location })
        }
        None => None,
    })
}

, , :


error[E0308]: mismatched types
  --> src\main.rs:43:34
   |
43 |             Some(Mistake { path, location })
   |                                  ^^^^^^^^
   |                                  |
   |                                  expected struct `std::string::String`, found &str
   |                                  help: try using a conversion method: `location: location.to_string()`
   |
   = note: expected type `std::string::String`
              found type `&str`

. location s. , . , . , , "Rust" "", . — ?


" malloc(). , - ! ?" , . :



" . ToString , ToOwned — , . :


fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
    s.clear();
    File::open(path)?.read_to_string(s)?;

    Ok(match s.find(",") {
        Some(index) => {
            let location = s[index..].to_string();
            Some(Mistake { path, location })
        }
        None => None,
    })
}

```bash
$ cargo run --quiet
sample.txt:  : ",  , !"
sample3.txt:  :  ",    "

. , , , , .



Rust- , . , , . , , , , . — !


,
. , , , , , , . , , .

". . ."


. , , , ...


" ..." , . -, - , - , , . , , , , .


". . ."


struct Mistake {
    path: &'static str,
    locations: Vec<String>,
}

, ( , !). , , , , , , , , , -. , , find() .


", find , . - . ?"


'str :


fn check(path: &'static str, s: &mut String) -> Result<Option<Mistake>, E> {
    s.clear();
    File::open(path)?.read_to_string(s)?;

    let locations: Vec<_> = s
        .match_indices(",")
        .map(|(index, slice)| slice.to_string())
        .collect();

    Ok(if locations.is_empty() {
        None
    } else {
        Some(Mistake { path, locations })
    })
}

", if !" , , . , , . map() collect().


Display, :


impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for location in &self.locations {
            write!(f, "{}:  : {:?}\n", self.path, location)?;
        }
        Ok(())
    }
}

sample.txt :


,  , !
    !
  ́ ́  —
 !

   ,    
  .
    
  .

" , , ", . , .


$ cargo run --quiet
sample4.txt:  : ","
sample4.txt:  : ","
sample4.txt:  : ","

. . . . . " location , ?"


    // check():
    let locations: Vec<_> = s
        .match_indices(",")
        .map(|(index, _)| {
            use std::cmp::{max, min};
            let start = max(0, index - 12);
            let end = min(index + 12, s.len());
            s[start..end].to_string()
        })
        .collect();

.: . , , ASCII, 21 UTF- .

(.: , .)


$ cargo run --quiet
sample4.txt:  : ",  "
sample4.txt:  : " , !"
sample4.txt:  : "   ,    "

" , - ." . " , -, , , . check(), report(). - — , , ."


" , , , , ."



.
. check() , report() — .
. — , .
:


struct Mistake {
    path: &'static str,

    text: String,
    locations: Vec<usize>,
}

fn check(path: &'static str) -> Result<Option<Mistake>, E> {
    let text = std::fs::read_to_string(path)?;

    let locations: Vec<_> = text.match_indices(",").map(|(index, _)| index).collect();

    Ok(if locations.is_empty() {
        None
    } else {
        Some(Mistake { path, text, locations })
    })
}

. , , Mistake, ( ?).
( UTF-8? — ..)


Mistake.


$ cargo run --quiet
warning: field is never used: `text`
  --> src\main.rs:28:5
   |
28 |     text: String,
   |     ^^^^^^^^^^^^
   |
   = note: #[warn(dead_code)] on by default

sample4.txt:  : 1
sample4.txt:  : 19
sample4.txt:  : 83

, - . . :


impl Mistake {
    fn line_bounds(&self, index: usize) -> (usize, usize) {
        let len = self.text.len();

        let before = &self.text[..index];
        let start = before.rfind("\n").map(|x| x + 1).unwrap_or(0);

        let after = &self.text[index + 1..];
        let end = after.find("\n").map(|x| x + index + 1).unwrap_or(len);

        (start, end)
    }
}

" rfind(). , , , unwrap_or()."


, :


impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for &location in &self.locations {
            let (start, end) = self.line_bounds(location);
            let line = &self.text[start..end];
            write!(f, "{}:  :\n", self.path)?;
            write!(f, "\n")?;
            write!(f, "      | {}", line)?;
            write!(f, "\n\n")?;
        }
        Ok(())
    }
}

$ cargo run --quiet
sample4.txt:  :

      | ,  , !

sample4.txt:  :

      | ,  , !

sample4.txt:  :

      |    ,    

. !


, - :


impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for &location in &self.locations {
            let (start, end) = self.line_bounds(location);
            let line = &self.text[start..end];

            let line_number = self.text[..start].matches("\n").count() + 1;

            write!(f, "{}:  :\n", self.path)?;
            write!(f, "\n")?;
            write!(f, "{:>8} | {}", line_number, line)?;
            write!(f, "\n\n")?;
        }
        Ok(())
    }
}

$ cargo run --quiet
sample4.txt:  :

      1 | ,  , !

sample4.txt:  :

      1 | ,  , !

sample4.txt:  :

      6 |    ,    

( , Rust ?)


— , . , Rust, ^. — Display, , , , , . 11 ( 8 + |, 3 ):
( technic93, utf-8, , , , . , — ..)


impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for &location in &self.locations {
            let (start, end) = self.line_bounds(location);
            let line = &self.text[start..end];

            let line_number = self.text[..start].matches("\n").count() + 1;
            let comma_index = self.text[start..location].chars().count();

            write!(f, "{}:  :\n\n", self.path)?;

            //    
            write!(f, "{:>8} | {}\n", line_number, line)?;

            //  
            write!(f, "{}^\n\n", " ".repeat(11 + comma_index))?;
        }
        Ok(())
    }
}

$ cargo run --quiet
sample4.txt:  :

      1 | ,  , !
           ^

sample4.txt:  :

      1 | ,  , !
                             ^

sample4.txt:  :

      6 |    ,    
                        ^

.


. 85 .


fn main() -> Result<(), std::io::Error> {
    let paths = ["sample4.txt"];

    //  
    let mut results = vec![];
    for path in &paths {
        let result = check(path)?;
        results.push(result);
    }

    //   
    for result in results {
        report(result);
    }

    Ok(())
}

fn report(result: Option<Mistake>) {
    if let Some(mistake) = result {
        println!("{}", mistake);
    }
}

struct Mistake {
    path: &'static str,

    text: String,
    locations: Vec<usize>,
}

use std::io::Error as E;

fn check(path: &'static str) -> Result<Option<Mistake>, E> {
    let text = std::fs::read_to_string(path)?;

    let locations: Vec<_> = text.match_indices(",").map(|(index, _)| index).collect();

    Ok(if locations.is_empty() {
        None
    } else {
        Some(Mistake {
            path,
            text,
            locations,
        })
    })
}

use std::fmt;

impl Mistake {
    fn line_bounds(&self, index: usize) -> (usize, usize) {
        let len = self.text.len();

        let before = &self.text[..index];
        let start = before.rfind("\n").map(|x| x + 1).unwrap_or(0);

        let after = &self.text[index + 1..];
        let end = after.find("\n").map(|x| x + index + 1).unwrap_or(len);

        (start, end)
    }
}

impl fmt::Display for Mistake {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for &location in &self.locations {
            let (start, end) = self.line_bounds(location);
            let line = &self.text[start..end];

            let line_number = self.text[..start].matches("\n").count() + 1;
            let comma_index = self.text[start..location].chars().count();

            write!(f, "{}:  :\n\n", self.path)?;

            //    
            write!(f, "{:>8} | {}\n", line_number, line)?;

            //  
            write!(f, "{}^\n\n", " ".repeat(11 + comma_index))?;
        }
        Ok(())
    }
}


, .


. , Rust. " Rust ", , , . , , , , .


( , FFI.)

Source: https://habr.com/ru/post/fr470129/


All Articles