Manajemen memori deklaratif

(terjemahan yang agak bebas dari artikel emosional besar yang dalam praktiknya menjembatani kemungkinan C dan Rust dalam hal memecahkan masalah bisnis dan menyelesaikan bug yang terkait dengan manajemen memori manual. Ini juga harus bermanfaat bagi orang yang berpengalaman dalam pengumpulan sampah - ada banyak perbedaan dalam hal semantik kurang dari yang terlihat - kira-kira per. )


Sejak saya tertarik pada Rust, rasanya seperti selamanya. Namun demikian, saya ingat dengan jelas untuk mengenal alat analisis pinjaman (alat pemeriksa pinjaman , yang selanjutnya disebut BCH ), disertai dengan sakit kepala dan keputusasaan. Tentu saja, saya bukan satu-satunya yang menderita - ada banyak artikel di Internet tentang topik komunikasi dengan hulu ledak. Namun, saya ingin menonjol dan menyoroti dalam artikel ini hulu ledak dari sudut pandang manfaat praktis , dan bukan hanya generator sakit kepala.


Dari waktu ke waktu, saya menemukan pendapat bahwa di Rust - manajemen memori manual ( mungkin tidak otomatis dengan GC, lalu apa lagi? - kira-kira Per. ), Namun, saya sepenuhnya tidak berbagi sudut pandang ini. Metode yang digunakan dalam Rust, saya sebut istilah " manajemen memori deklaratif ". Kenapa begitu - sekarang saya akan tunjukkan.


Aturan untuk pendaftaran


Alih-alih berteori, mari kita menulis sesuatu yang bermanfaat.


Meet Overbook - Rumah Penerbitan Fiksi!


Seperti penerbit mana pun, Overbook memiliki aturan desain. Lebih tepatnya, hanya ada satu aturan, sesederhana pintu - tanpa koma . Overbuk dengan tulus percaya bahwa koma adalah konsekuensi dari kemalasan hak cipta dan - kutipan - "harus dimusnahkan sebagai sebuah fenomena." Misalnya, ungkapan "Dia membaca dan tertawa" - bagus, cocok. "Dia membaca, dan kemudian tertawa" - membutuhkan koreksi.



Akan tampak lebih sederhana di mana pun, namun, penulis secara teratur menangkap Overbuk tentang ketidakpatuhan patologis dengan aturan ini! Seolah aturan seperti itu tidak ada sama sekali, keterlaluan! Kami harus memeriksa ulang semuanya. Secara manual. Selain itu, jika draft diminta oleh penerbit untuk diedit, penulis dapat mengirim versi yang telah diperbaiki di satu, tetapi rusak di tempat lain, dan karena itu semuanya harus diperiksa ulang sejak awal. Bisnis tidak mentolerir sikap lalai seperti itu terhadap waktu kerja, dan kebutuhan muncul untuk mengotomatiskan proses menangkap "kemalasan penulis" dengan sendirinya. Misalnya, program komputer. Ya ya


Robin bergegas menyelamatkan


Robin adalah salah satu karyawan penerbit yang mengajukan diri untuk membantu menulis program, karena dia tahu pemrograman - ini adalah keberuntungan! Benar, semua orang di universitas, termasuk Robin, diajari C dan Java, dan Java VM dilarang tanpa alasan untuk menginstal Java dari penerbit - itulah gilirannya! Yah, biarlah C, sudah banyak yang ditulis dalam C, bahasanya pasti dan terverifikasi. Semuanya akan berjalan sebagaimana mestinya, INFA 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/id470129/


All Articles