thảo luận Nhật kí tự học lập trình Rust - phát triển ứng dụng Desktop (Both CLI + GUI)

  • Người tạo chủ đề Người tạo chủ đề duykhanh471
  • Ngày bắt đầu Ngày bắt đầu
Chúng nó lừa đấy, trần đời mà dễ học chỉ có js, thế nên js mới lắm tai họa.
À do tui không học theo chương trình nên thấy khó. Golang nó bắt khai báo type nên code mỏi tay hơn tí nhưng khi cần nhìn biết ngay input của function là cái gì, vì cái đấy nên tui mới học nó.
Keyword của nó cũng ít hơn nữa đỡ phải nhớ nhiều :shame:
 
Hình như Rust cho performance tối ưu hơn
Well. It depends.
Nhắc đến go là nhắc đến concurrent. Rust là memory safe. Tuỳ bài toán mà áp dụng chứ nói về perf tối ưu nhất thì ai cũng viết assembly rồi.
Perf mấy thằng này same same nhau thôi. Code web không thấy khác biệt.
Btw, Go dùng xử lý concurrent vẫn nhanh hơn nhé.
 
Well. It depends.
Nhắc đến go là nhắc đến concurrent. Rust là memory safe. Tuỳ bài toán mà áp dụng chứ nói về perf tối ưu nhất thì ai cũng viết assembly rồi.
Perf mấy thằng này same same nhau thôi. Code web không thấy khác biệt.
Btw, Go dùng xử lý concurrent vẫn nhanh hơn nhé.
các hạ cao minh quá, cho em xin roadmap go được không PHP cùi quá
 
@duykhanh471 thằng này làm desktop app cross platform ngon không thím? Framework, tool cộ các thứ mature hết chưa?

Mình nghĩ là ngon, bác viết nền bằng Rust rồi dùng Frontend ngoài, ví dụ như Anki viết bằng cả Rust với Python, hay khá nhiều ứng dụng port từ Electron sang Tauri. Còn về công cụ thuần Rust thì khả năng cao là chưa, nhưng dùng được khá nhiều (egui, iced-rs, đây là cho Desktop, còn bên Web Dev thì tuyệt, ý mình là backend, front-end nghe bảo cũng ổn vì nó là WASM, nhưng nhiều người vẫn gộp cả hệ sinh thái JS vào nữa)
 
giờ thấy Zig khá hay, đơn giản như Golang, tốc độ nhanh hơn C, compile nhanh hơn Rust
Không liên quan nhưng bác đã đọc bài #1 chưa, mình có ghi là không thảo luận ưu nhược điểm của các ngôn ngữ mà. Khá cầu toàn nhưng có Thread Rust tổng bàn mấy cái như của bạn nên bạn ném sang đó nha
 
Sửa lần cuối:
Viết lại Buukuru và đã cải thiện được định dạng =)) buukuru (https://codeberg.org/duykhanh471/buukuru)

Hình chụp trên Kindle

screenshot_2024_07_31T11_31_46+0700.png
 
Định làm pull request nhưng lằng nhằng quá, gửi thím vài comment cho badpeggy: không nên đổi qua lại giữa PathBuf và String (hai cái này sử dụng cho hai mục đích khác nhau). Ví dụ file utils.rs thím có thể viết như sau:

C-like:
use std::{ffi::OsStr, fs::DirEntry, path::{Path, PathBuf}, vec};

use crate::checker::ImageProcessor;

pub fn check_img_format(filename: &Path) -> bool {
    let image_format: Vec<&str> = vec!["jpg", "jpeg", "jpe", "jfif", "jfi", "jif", "png", "bmp", "gif"];
    filename.extension().and_then(OsStr::to_str).is_some_and(|ext| image_format.contains(&ext))
}

pub fn image_filter(paths: impl IntoIterator<Item = Result<DirEntry, std::io::Error>>) -> Result<Vec<PathBuf>, std::io::Error> {
    paths.into_iter()
    .map(|f| f.map(|p| p.path()))
    .filter(|p| p.as_ref().is_ok_and(|f| check_img_format(&f))).collect()
}

pub fn image_processing(paths: impl Iterator<Item = PathBuf>, img_processor: ImageProcessor) {
    for current_path in paths {
        if let Some(broken_img) = img_processor.check_broken_image(&current_path) {
            println!("Image: {:?} | {}", broken_img.path, broken_img.err_msg);
        }
    }
}

Hàm check_broken_image trong checker.rs có thể tham số hóa thành kiểu
check_broken_image<P: AsRef<Path> + Copy>(&self, img_path: P) -> Option<BrokenImageInfo<P>>

Hàm main giờ không có kiểm tra lỗi cho panic khi unwrap, thím có thể bỏ luôn unwrap đi bang cách thêm kiểu trả về cho main. Ví dụ thế này:

C-like:
fn main() -> Result<(), impl Error> {
    let imgs = fs::read_dir("./")?;
    let img_processor = checker::ImageProcessor {};
    image_processing(image_filter(imgs)?.into_iter(), img_processor);
    Ok::<_, std::io::Error>(())
}

3 file mình sửa lại như sau:
C-like:
use std::{error::Error, fs};

use utils::{image_filter, image_processing};
mod checker;
mod utils;

fn main() -> Result<(), impl Error> {
    let imgs = fs::read_dir("./")?;
    let img_processor = checker::ImageProcessor {};
    image_processing(image_filter(imgs)?.into_iter(), img_processor);
    Ok::<_, std::io::Error>(())
}
C-like:
use std::{fs, path::Path};

pub struct BrokenImageInfo<P> {
    pub path: P,
    pub err_msg: String
}
#[derive(Debug, Clone)]
pub struct ImageProcessor {}

impl ImageProcessor {
    pub fn check_broken_image<P: AsRef<Path> + Copy>(&self, img_path: P) -> Option<BrokenImageInfo<P>>{
        if let Err(e) = image::open(img_path) {
            let error_string = e.to_string();
            if error_string.contains("spectral selection is not allowed in non-progressive scan") {
                println!("Skipped");
            }
            Some(BrokenImageInfo {
                path: img_path,
                err_msg: error_string,
            })
        } else {
            None
        }
    }
   
    pub fn delete_img<P: AsRef<Path>>(&self, path: P) {
        fs::remove_file(path).expect("");
    }
}
C-like:
use std::{ffi::OsStr, fs::DirEntry, path::{Path, PathBuf}, vec};

use crate::checker::ImageProcessor;

pub fn check_img_format(filename: &Path) -> bool {
    let image_format: Vec<&str> = vec!["jpg", "jpeg", "jpe", "jfif", "jfi", "jif", "png", "bmp", "gif"];
    filename.extension().and_then(OsStr::to_str).is_some_and(|ext| image_format.contains(&ext))
}

pub fn image_filter(paths: impl IntoIterator<Item = Result<DirEntry, std::io::Error>>) -> Result<Vec<PathBuf>, std::io::Error> {
    paths.into_iter()
    .map(|f| f.map(|p| p.path()))
    .filter(|p| p.as_ref().is_ok_and(|f| check_img_format(&f))).collect()
}

pub fn image_processing(paths: impl Iterator<Item = PathBuf>, img_processor: ImageProcessor) {
    for current_path in paths {
        if let Some(broken_img) = img_processor.check_broken_image(&current_path) {
            println!("Image: {:?} | {}", broken_img.path, broken_img.err_msg);
        }
    }
}
 
Sửa lần cuối:
Định làm pull request nhưng lằng nhằng quá, gửi thím vài comment cho badpeggy: không nên đổi qua lại giữa PathBuf và String (hai cái này sử dụng cho hai mục đích khác nhau). Ví dụ file utils.rs thím có thể viết như sau
Không liên quan lắm nhưng nếu đã thêm impl IntoIterator thì không cần chuyển sang iterator nữa :LOL: into_iter() hoặc có thể đang có nhiều hơn 1 ReadDir (Nếu mình nhớ không nhầm thì chỉ có một và nếu gọi nó ra thì nó sẽ trả Result<DirEntry, io:Error>)

C-like:
image_filter(paths: impl IntoIterator<Item = Result<DirEntry, std::io::Error>>) -> Result<Vec<PathBuf>, std::io::Error> {
paths.into_iter()

Còn lại thì mình đang đọc, có khá nhiều cái hay mình chưa dùng bao giờ như is_some_and(), trước trong Forum có ông bảo dùng filter_map() cũng chỉ lấy các giá trị trả về Some.

Còn về AsRef thì lần đầu dùng, mình mới lập trình nên chưa động bao giờ vào Generic với mấy cái liên quan, mình cũng mới thử đọc qua nên chưa nắm rõ lắm.

Dù sao bạn sửa phần code lại nhìn cải thiện hơn nhiều, thanks bạn nhìu :>>
 
Thêm comment nữa cho thím, hàm
C-like:
pub fn check_img_format(filename: &Path) -> bool {
    let image_format: Vec<&str> = vec!["jpg", "jpeg", "jpe", "jfif", "jfi", "jif", "png", "bmp", "gif"];
    filename.extension().and_then(OsStr::to_str).is_some_and(|ext| image_format.contains(&ext))
}
Sẽ được gọi nhiều lần (mỗi lần cho một file), và image_format nói chung là một vector không thay đổi (chứa các image extensions).
Để có hiệu suất cao hơn thì image_format có thể đặt thành một static array, điều này tránh được việc khởi tạo đi khởi tạo lại vector image_format mỗi lần hàm được gọi.
Tóm lại code mới sẽ như sau:
C-like:
pub fn check_img_format(filename: &Path) -> bool {
    static IMAGE_FORMAT: &[&'static str; 9] = &[
        "jpg", "jpeg", "jpe", "jfif", "jfi", "jif", "png", "bmp", "gif",
    ];
    filename.extension().and_then(OsStr::to_str).is_some_and(|ext| IMAGE_FORMAT.contains(&ext))
}
Hoặc tốt hơn nữa về mặt thuật toán thì IMAGE_FORMAT có thể để thành một static HashSet, nhưng khai báo phức tạp hơn (do không có cách nào khởi tạo một HashSet trong compile time):
C-like:
use std::sync::LazyLock;
static IMAGE_FORMAT_SET: LazyLock<std::collections::HashSet<&'static str>> = LazyLock::new(|| {
    HashSet::from([
        "jpg", "jpeg", "jpe", "jfif", "jfi", "jif", "png", "bmp", "gif",
    ])
});
 
Không liên quan lắm nhưng nếu đã thêm impl IntoIterator thì không cần chuyển sang iterator nữa :LOL: into_iter() hoặc có thể đang có nhiều hơn 1 ReadDir (Nếu mình nhớ không nhầm thì chỉ có một và nếu gọi nó ra thì nó sẽ trả Result<DirEntry, io:Error>)
Không phải, thứ nhất "nhưng nếu đã thêm" là một cách dùng từ sai, vì dùng impl là cách để giảm các ràng buộc cho tham số (mà không phải là tăng): lúc đầu hàm yêu cầu tham số là một ReadDir, nhưng logic của hàm chỉ là một vòng lặp duy nhất qua tất cả các entry (có kiểu DirEntry), nên tham số chỉ cần là một kiểu bất kỳ có cài đặt trait IntoIterator.

Thứ hai, vẫn phải chuyển sang Iterator (bằng lời gọi .into_iter()) vì paths ở đây không phải là một Iterator, nó chỉ là một kiểu có cài đặt trait IntoIterator. Một ví dụ khác là vector không phải là Iterator, nó chỉ cài đặt trait IntoIterator (và Iterator).
 
Mình đã ngồi sửa lại code, đọc và ngẫm khá nhiều từ nhận xét của @Konstante và sửa lại code, học được rất nhiều thứ hay, cảm ơn bạn rất nhìu (Xin lỗi vì trả lời muộn nhưng có nhiều cái mình cần học thêm quá):
- "vì dùng impl là cách để giảm các ràng buộc cho tham số": Ý này thì mình không hiểu lắm, có phải nó liên quan đến cái này không, cái phần quote ở dưới á bác
Thứ hai, vẫn phải chuyển sang Iterator (bằng lời gọi .into_iter()) vì paths ở đây không phải là một Iterator, nó chỉ là một kiểu có cài đặt trait IntoIterator
- "Một ví dụ khác là vector không phải là Iterator, nó chỉ cài đặt trait IntoIterator (và Iterator)": Tức là về cơ bản thì mình nên coi (Nó đúng là vậy chứ không hẳn là nên coi) Iterator là một Trait trong bất kì một dãy kiểu dữ liệu kiểu như Vec đúng không bác?
Sẽ được gọi nhiều lần (mỗi lần cho một file), và image_format nói chung là một vector không thay đổi (chứa các image extensions).
Mặc dù hơi ngu nhưng giờ mình mới biết thêm là nếu chạy trên một số lượng lớn dữ liệu thì kết quả khác hơn thật. Tức là mỗi lần mình gọi check_image() thì đồng nghĩa với việc let khởi tạo lại dãy IMAGE_FORMAT. Vậy thì liệu dùng const có tốt hơn là sử dụng &'static str (Hình như cái này (&'static) dính lifetime đúng không bác) hoặc HashMap ở trên không? Vì định dạng ảnh sẽ không thay đổi.
fn main() -> Result<(), impl Error> {
Tại sao lại không sử dụng std::io:Error ở đây vậy fen? Mình có thử đọc khá nhiều code thì thấy nhiều người dùng anyhow hoặc Box dyn.

Một cái nữa là thực hiện impl trên kiểu biến khi khai báo hàm là sao vậy? Mình có sử dụng impl khi thêm chức năng hoặc trait vào trong một struct nhất định, thi thoảng cũng có kiểu biến hoặc bên async fn thì phải chỉnh giá trị trả về thành impl <Future.... (Mình sẽ tự tìm hiểu thêm tiếp, tự nhiên ngồi viết câu trả lời thì nghĩ ra)
pub fn check_broken_image<P: AsRef<Path> + Copy>(&self, img_path: P) -> Option<BrokenImageInfo<P>>{
Việc sử dụng AsRef ở đây để hỗ trợ Borrowing cho Generic đúng không bác. Mình đọc Thread này trên StackOverflow thì thấy bác ấy giải thích là việc sử dụng AsRef được impl dưới dạng trait để hỗ trợ cho cả biến đấy được sử dụng kiểu &User thay vì là &user.name (Phải khai báo user và sử dụng & - Cái này có phải sử dụng #(Copy) đầu tên struct đúng không bác).

Còn nghĩ thêm nữa, có lẽ là về vấn đề Test. Mình cũng có thấy nhiều người trong các Project dù lớn hay nhỏ đều viết Test và học từ HtDP (How To Design Program) thì Test gần như là một phần đầu tiên luôn. Nhưng theo bác thì việc "test" cho dữ liệu ngoài, ở đây là các ảnh thì nên làm như thế nào? Hoặc mình xin gợi ý để cải thiện phần này (Mình cũng có thể hình dung tương đối về input và expected value và muốn tìm hiểu thêm)

Cảm ơn bác rất nhìu một lần nữa. Mình sẽ viết thêm ứng dụng khác để cập nhật những điều mới học
 
.map(|f| f.map(|p| p.path()))
Khá lạ nhưng đóng map() hai lần thì bác vẫn chuyển DirEntry qua PathBuf được mà vẫn giữ được Result.
.filter(|p| p.as_ref().is_ok_and(|f| check_img_format(&f))).collect()
Mình nhìn là p thuộc kiểu &Result<PathBuf, std::io::Error>, tức là ở đây khi dùng as_ref() thì sẽ chỉ lấy PathBuf dưới dạng &PathBuf và không động chạm gì đến phần còn lại của Result?

Mình có đọc được định nghĩa này về Shared Reference:
A shared reference to a type T has type &T. A reference value is made withthe & operator. The * operator "dereferences" a reference, yielding itsvalue.

Tức là về cơ bản là Borrowing đúng không bác? (hỏi ngu lần tiếp). Nhưng việc sử dụng & không phải bao giờ cũng được có phải là do nó chưa impl<T> AsRef<T> không bác? (Hỏi ngu tiếp)
 

Thống kê chủ đề

Ngày tạo
duykhanh471,
Người trả lời cuối
emlameo4`,
Trả lời
142
Lượt xem
27.186
Quay lại
Lên đầu trang