Declarative memory management

(a rather free translation of a huge emotional article that in practice bridges the possibilities of C and Rust in terms of solving business problems and resolving bugs related to manual memory management. It should also be useful for people with experience in garbage collection - there are many differences in terms of semantics less than it might seem - approx. per. )







From the moment I became interested in Rust, it seemed like an eternity. Nevertheless, I clearly remember getting to know the borrowing analyzer ( borrow checker , hereinafter referred to as BC - approx. Per. ), Accompanied by a headache and despair. Of course I'm not the only one suffering - there are plenty of articles on the Internet on the topic of communication with warheads. However, I would like to stand out and highlight in this article the warhead from the point of view of practical benefits , and not just a headache generator.







From time to time, I come across opinions that in Rust - manual memory management ( probably since it’s not automatic with GC, then what else? - approx. Per. ), But I don’t share this point of view at all. The method used in Rust, I call the term " declarative memory management ". Why so - now I will show.







Rules for registration



Instead of theorizing, let's write something useful.







Meet Overbook - Fiction Publishing House!







Like any publisher, Overbook has design rules. More precisely, there is only one rule, simple as doors - no commas . Overbuk sincerely believes that commas are a consequence of copyright laziness and - quote - "must be destroyed as a phenomenon." For example, the phrase "She read and laughed" - good, suitable. "She read, and then laughed," - requires correction.













It would seem simpler nowhere, however, authors regularly catch on Overbuk on pathological non-compliance with this rule! As if such a rule does not exist at all, outrageous! We have to double-check everything. Manually. Moreover, if a draft is requested by the publisher for editing, the author can send a version that has been fixed in one, but corrupted in another place, and therefore everything has to be double-checked from the very beginning. Business does not tolerate such a negligent attitude to working time, and the need arose to automate the process of catching "author's laziness" by itself. For example, a computer program. Yes, yes?







Robin hurries to the rescue



Robin is one of the employees of the publishing house who volunteered to help with writing the program, because he knew programming - this is good luck! True, everyone at the university, including Robin, was taught C and Java, and the Java publishing house was forbidden to install Java VM for no reason - that's the turn! Well, let it be C, a lot of things have been written in C, the language is sure and verified. Everything will go as it should, 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(())
}
      
      





:









, , . 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.)








All Articles