Rust: Rust Error Handling: `Result` and the `?` Operator

Last updated: 2026-08-26

Error handling is one of Rust’s most remarkable design features—it doesn’t rely on exceptions, but instead uses the type system to encode “potential failure” into the return type, and the compiler ensures that you never overlook any errors.

Rust does not have try-catch or throw. Instead, it uses the Result<T, E> enumeration and the ? operator—which separate error handling from normal business logic, resulting in code that is both safe and concise.


1. A Story of Handling Issues from Ordering Takeout to Picking It Up

(1) The Real-Life Food Delivery Process

Xiao Ming is working overtime today and has decided to order takeout. There are several points in the process where things could go wrong:

Step Normal Process Possible Errors
1. Find a restaurant Open the food delivery app and search for a restaurant Can't find a restaurant
2. Load Menu Browse Menu Items Menu Load Failed (Network Timeout)
3. Submit Payment Pay Payment Failed (Insufficient Balance)
4. Awaiting Preparation Wait 30 minutes Store Cancels Order
5. Pick Up Receive Takeout Discover the Order Has Been Canceled Upon Pickup

Each step may succeed (Ok) or fail (Err). Let's model this using Rust's approach:

RUST
// ============================================
// Use Result to simulate every step of ordering takeout
// ============================================

// Defining Possible Errors
#[derive(Debug)]
enum OrderError {
    RestaurantNotFound,
    MenuLoadFailed,
    PaymentFailed(String),
    OrderCancelled,
}

// Simulate Store Search
fn find_restaurant(name: &str) -> Result<String, OrderError> {
    let available = vec!["PizzaHouse", "SushiBar", "NoodleShop"];
    if available.contains(&name) {
        Ok(format!("Found: {}", name))
    } else {
        Err(OrderError::RestaurantNotFound)
    }
}

// Simulate the Load Menu
fn load_menu(restaurant: &str) -> Result<Vec<&str>, OrderError> {
    if restaurant.contains("Pizza") {
        Ok(vec!["Margherita", "Pepperoni", "Hawaiian"])
    } else {
        Err(OrderError::MenuLoadFailed)
    }
}

// Simulated Payment
fn process_payment(amount: f64) -> Result<String, OrderError> {
    if amount < 100.0 {
        Ok(format!("Paid: ${:.2}", amount))
    } else {
        Err(OrderError::PaymentFailed("Insufficient balance".into()))
    }
}

fn main() {
    // Go through the entire process from start to finish
    let restaurant = find_restaurant("PizzaHouse");
    match restaurant {
        Ok(msg) => println!("Step 1: {}", msg),
        Err(e) => println!("Step 1 failed: {:?}", e),
    }
}

At each step, a return value of Result<T, E>Ok(T) indicates success, while Err(E) indicates failure. The caller must explicitly handle both possibilities; it is not possible to "forget to handle errors."



2. Conceptual Diagrams

The following Mermaid flowchart illustrates the four main paths for handling Result<T, E> errors: detailed processing via match, operator propagation via ?, fast evaluation via unwrap/expect, and a panic:

100%
graph TB
    A["Result&lt;T, E&gt;"] --> B["Ok(T)<br/>Success"]
    A --> C["Err(E)<br/>Failure"]

    B --> D["Continue execution<br/>Standard Procedure"]

    C --> E["match Processing<br/>Fine-Grained Branch Control"]
    C --> F["? Operators<br/>Propagation Error"]
    C --> G["unwrap / expect<br/>Quick Value Retrieval"]

    E --> H["For different errors<br/>Handle them separately"]
    F --> I["Caller Function<br/>Receive Err"]
    G --> J["panic!<br/>Program Crash"]

    H --> K["Restore Default Values<br/>Or retry logic"]
    I --> L["match in main<br/>Centralized Processing"]

    style A fill:#e1f5fe,stroke:#0288d1
    style B fill:#c8e6c9,stroke:#388e3c
    style C fill:#ffcdd2,stroke:#d32f2f
    style J fill:#ffcdd2,stroke:#d32f2f


3. What You'll Learn



4. Core Concepts

100%
graph TB
    A[Error Handling Strategies] --> B[Recoverable errors<br>Recoverable]
    A --> C[Unrecoverable error<br>Unrecoverable]

    B --> D["Result<T, E>"]
    D --> E["Ok(T) Success Score"]
    D --> F["Err(E) Error value"]

    F --> G["match Precision Processing"]
    F --> H["? Top-down communication"]
    F --> I["unwrap/expect Quick Value Retrieval<br>(There are risks)"]

    C --> J["panic!"]
    J --> K["The program crashed and exited"]
    J --> L["Applicable:Bug/Irreversible state"]

    B -.-> M["Custom Error Types"]
    M --> N["Implementation Display + Debug"]
    M --> O["From trait Convert"]

(1) Comparison of Four Error-Handling Strategies

Strategy Use Case Advantages Disadvantages
panic! Unrecoverable errors (such as array out-of-bounds or assertion failures) Fast failure to expose the problem The program crashes immediately
unwrap / expect Prototype development / Guaranteed not to fail Concise code Panics immediately when an error occurs—not very elegant
match / if let Requires different handling for different errors Fine-grained control over error-handling logic Verbose code
? Operator Propagates errors between functions; handled uniformly at the top level Most concise; keeps the main logic clear Must be used in functions that return a Result

(2) Result<T, E> Quick Reference Guide

Method Signature Purpose Behavior on Failure
unwrap() Result<T,E> -> T Extract the value from Ok panic!
expect(msg) Result<T,E> -> T Retrieve the value from "Ok" and customize the panic message panic!(msg)
unwrap_or(default) Result<T,E> -> T Returns a value on success; returns a default value on failure Returns a default value
unwrap_or_else(fn) Result<T,E> -> T Return a value on success; execute the closure on failure Execute the closure
is_ok() Result<T,E> -> bool Check if successful
is_err() Result<T,E> -> bool Check for failure
ok() Result<T,E> -> Option<T> Convert to Option None
err() Result<T,E> -> Option<E> To Option None
map(fn) Result<T,E> -> Result<U,E> Convert success value Unchanged
map_err(fn) Result<T,E> -> Result<T,F> Convert incorrect values Convert error types
and_then(fn) Result<T,E> -> Result<U,E> Follow-up Operations in Chained Calls Short-Circuit Evaluation


5. Examples

▶ Example 1: Basic “Result” and “match” Handling (Difficulty ⭐)

Output:

TEXT 📖 Display only
=== match Processing ===
10 / 2 = <result>
Error: <msg>
10 / 0 = <result>
Error: <msg>

=== unwrap_or Default value ===
10 / 2 = <result1>
10 / 0 = <result2> (default)

=== unwrap_or_else Closure ===
Warning: <e>, using default
Result: <result3>
Will not reach here
RUST
// ============================================
// Use Result to handle division-by-zero errors
// ============================================

fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    // Use match to handle both success and failure scenarios
    println!("=== match Processing ===");
    match safe_divide(10.0, 2.0) {
        Ok(result) => println!("10 / 2 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    match safe_divide(10.0, 0.0) {
        Ok(result) => println!("10 / 0 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    // Use unwrap_or to provide a default value
    println!("\n=== unwrap_or Default value ===");
    let result1 = safe_divide(10.0, 2.0).unwrap_or(0.0);
    let result2 = safe_divide(10.0, 0.0).unwrap_or(0.0);
    println!("10 / 2 = {}", result1);
    println!("10 / 0 = {} (default)", result2);

    // Use unwrap_or_else to execute a closure
    println!("\n=== unwrap_or_else Closure ===");
    let result3 = safe_divide(10.0, 2.0).unwrap_or_else(|e| {
        eprintln!("Warning: {}, using default", e);
        0.0
    });
    println!("Result: {}", result3);

    // ⚠️ unwrap will panic (Uncomment the code below to try it out)
    // let crash = safe_divide(10.0, 0.0).unwrap();
    // println!("Will not reach here");
}

Output:

TEXT 📖 Display only
=== match Processing ===
10 / 2 = 5
Error: Division by zero

=== unwrap_or Default value ===
10 / 2 = 5
10 / 0 = 0 (default)

=== unwrap_or_else Closure ===
Result: 5

match Offers the most comprehensive handling—you can write separate handling logic for Ok and Err. unwrap_or and unwrap_or_else are shortcuts: they provide default values in case of failure. unwrap() is the riskiest—it assumes success is guaranteed, and crashes immediately if it fails.


▶ Example 2: ? Operator—Chained Propagation Error (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
--- <file> (0 bytes) ---
<&content[..content.len().min(80)]>
Failed to read '<file>': <e>
RUST
// ============================================
// ? Operators: Error propagation with Result
// Only when returning Result can only be used within the function ?
// ============================================

use std::fs::File;
use std::io::{self, Read};

// Read the entire contents of the file
// ? indicates:If File::open Failure,Return Now Err
//         If read_to_string Failure,Return Now Err
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;          // Open the file,If it fails, return
    let mut content = String::new();
    file.read_to_string(&mut content)?;         // Read the content,If it fails, return
    Ok(content)
}

// Do not use ? An equivalent way of writing——The amount of code has doubled
fn read_file_without_question(path: &str) -> Result<String, io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut content = String::new();
    match file.read_to_string(&mut content) {
        Ok(_) => Ok(content),
        Err(e) => Err(e),
    }
}

// Chain Call ?——More concise
fn read_file_chain(path: &str) -> Result<String, io::Error> {
    let mut content = String::new();
    File::open(path)?.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    // Test files that can and cannot be read, respectively
    let files = vec!["Cargo.toml", "nonexistent.txt"];

    for file in &files {
        match read_file(file) {
            Ok(content) => {
                println!("--- {} ({} bytes) ---", file, content.len());
                println!("{}", &content[..content.len().min(80)]);
            }
            Err(e) => {
                println!("Failed to read '{}': {}", file, e);
            }
        }
    }
}

Output:

TEXT 📖 Display only
--- Cargo.toml (42 bytes) ---
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

Failed to read 'nonexistent.txt': The system cannot find the file specified. (os error 2)

The ? operator is the essence of error handling in Rust: it serves as a shorthand for "return early on failure." expr? is equivalent to match expr { Ok(v) => v, Err(e) => return Err(e.into()) }. Note that ? automatically calls From::from to perform error type conversion—this is key to its ability to propagate across different error types.


▶ Example 3: Custom Error Types—Implementing Display + Debug (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
[<description>] OK: <result>
[<description>] Error: <e>
RUST
// ============================================
// Custom Error Types:Make Your Error Messages More Informative
// To be implemented std::fmt::Display + std::fmt::Debug
// ============================================

use std::fmt;
use std::num::ParseIntError;

// Custom Error Enumeration
#[derive(Debug)]
enum AppError {
    /// Input is empty
    EmptyInput,
    /// Failed to parse the number,Includes the original string
    ParseFailed(String),
    /// The value is outside the allowed range
    OutOfRange { value: i32, min: i32, max: i32 },
    /// Division by Zero
    DivisionByZero,
}

// Implementation Display——Control the error messages users see
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::EmptyInput => {
                write!(f, "Input cannot be empty")
            }
            AppError::ParseFailed(input) => {
                write!(f, "Failed to parse '{}' as a number", input)
            }
            AppError::OutOfRange { value, min, max } => {
                write!(f, "Value {} is out of range [{}, {}]", value, min, max)
            }
            AppError::DivisionByZero => {
                write!(f, "Division by zero is not allowed")
            }
        }
    }
}

// Implement From<ParseIntError> — let ? operator convert automatically
impl From<ParseIntError> for AppError {
    fn from(_: ParseIntError) -> Self {
        AppError::ParseFailed("unknown".into())
    }
}

// Processing User Input:Analyze and Verify
fn process_input(input: &str, divisor: i32) -> Result<i32, AppError> {
    if input.is_empty() {
        return Err(AppError::EmptyInput);
    }
    let value: i32 = input.parse().map_err(|_| {
        // Manually Convert Error Types
        AppError::ParseFailed(input.to_string())
    })?;
    if value < -100 || value > 100 {
        return Err(AppError::OutOfRange {
            value,
            min: -100,
            max: 100,
        });
    }
    if divisor == 0 {
        return Err(AppError::DivisionByZero);
    }
    Ok(value / divisor)
}

fn main() {
    let test_cases = vec![
        ("42", 2,    "Normal case"),
        ("",   1,    "Empty input"),
        ("abc", 1,   "Parse error"),
        ("999", 1,   "Out of range"),
        ("50",  0,   "Division by zero"),
    ];

    for (input, divisor, description) in test_cases {
        match process_input(input, divisor) {
            Ok(result) => println!("[{}] OK: {}", description, result),
            Err(e) => println!("[{}] Error: {}", description, e),
        }
    }
}

Output:

TEXT 📖 Display only
[Normal case] OK: 21
[Empty input] Error: Input cannot be empty
[Parse error] Error: Failed to parse 'abc' as a number
[Out of range] Error: Value 999 is out of range [-100, 100]
[Division by zero] Error: Division by zero is not allowed

To define a custom error type, you need to implement Display (the error message displayed to the user) and Debug (debug output for {:?}). Implementing the From<T> trait allows the ? operator to automatically convert a specific error type to your custom type—this is the core mechanism that enables ? to propagate across types.


▶ Example 4: panic! vs. Strategies for Handling Error Return Values (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
Default port: <get_default_port()>
sqrt(16) = <sqrt_unchecked(value)>
Parsed: <n>
Parse failed: <e>
BMI: <bmi>
BMI error: <e>
First element: <first>
RUST
// ============================================
// Demo panic! Scenarios Where Error Return Values Are Appropriate
// panic! → Unrecoverable error(Bug/Assertion Failed)
// Result → Recoverable errors(User Input/IOFailure)
// ============================================

// --- Suitable for panic! the scene ---

/// Read the port number from the configuration file
/// If the configuration file is missing,This is part of the program Bug,panic That makes sense.
fn get_default_port() -> u16 {
    // This value is hard-coded in the code.,It's impossible for the resolution to fail.
    "8080"
        .parse()
        .expect("Hardcoded port number is invalid")
}

/// A function that accepts only positive integers
/// Passing a negative number indicates that the caller has Bug,panic Quickly Identify Problems
fn sqrt_unchecked(x: i32) -> f64 {
    if x < 0 {
        panic!("sqrt_unchecked called with negative value: {}", x);
    }
    (x as f64).sqrt()
}

// --- Suitable for Result the scene ---

/// Parsing Numbers from User Input
/// It is normal for users to enter the wrong format.,Should be returned Result
fn parse_user_input(input: &str) -> Result<i32, String> {
    input
        .parse()
        .map_err(|_| format!("'{}' is not a valid integer", input))
}

/// Calculate Body Mass Index(BMI)
/// Weight is 0 A negative number may indicate a data error.,Not a program Bug
fn calculate_bmi(weight_kg: f64, height_m: f64) -> Result<f64, String> {
    if weight_kg <= 0.0 {
        return Err("Weight must be positive".to_string());
    }
    if height_m <= 0.0 {
        return Err("Height must be positive".to_string());
    }
    Ok(weight_kg / (height_m * height_m))
}

fn main() {
    // Scene 1:panic Used for unrecoverable errors
    println!("Default port: {}", get_default_port());

    // Scene 2:panic Used for assertions——Invalid parameter
    let value = 16;
    println!("sqrt({}) = {}", value, sqrt_unchecked(value));

    // Scene 3:Result Used for recoverable errors——User Input
    let inputs = vec!["42", "hello", "-5"];
    for input in inputs {
        match parse_user_input(input) {
            Ok(n) => println!("Parsed: {}", n),
            Err(e) => println!("Parse failed: {}", e),
        }
    }

    // Scene 4:Result Used for business logic validation
    let bmi_cases = vec![
        (70.0, 1.75),
        (0.0, 1.70),
        (65.0, -0.5),
    ];
    for (weight, height) in bmi_cases {
        match calculate_bmi(weight, height) {
            Ok(bmi) => println!("BMI: {:.1}", bmi),
            Err(e) => println!("BMI error: {}", e),
        }
    }

    // Scene 5: expect's panic message helps with debugging
    let numbers = vec![10, 20, 30];
    let first = numbers.first().expect("Vector should not be empty");
    println!("First element: {}", first);
}

Output:

TEXT 📖 Display only
Default port: 8080
sqrt(16) = 4
Parsed: 42
Parse failed: 'hello' is not a valid integer
Parsed: -5
BMI: 22.9
BMI error: Weight must be positive
BMI error: Height must be positive
First element: 10

Core Principles for Choosing a Strategy: panic! is used for "bugs in the program itself" (invalid hard-coded data, parameters that violate conventions, array index out-of-bounds); Result is used for "exceptions caused by the external environment or user input" (I/O errors, parsing failures, business validation failures). Simply put: Use Result for issues you can fix, and use panic for those you can’t!



6. An Introduction to the Concepts of anyhow and thiserror

In production-grade Rust projects, two community crates are widely used to simplify error handling:

Crate Primary Use Use Cases Key Features
anyhow Error propagation (caller's perspective) Application main function, CLI tools, scripts anyhow::Result<T>, .context() Provide context for the error
thiserror Error definition (from the library author's perspective) Error types in the library's public API Implemented using the derive macro to automatically generate Display + Error
RUST
// anyhow Style(Concept Examples,No execution required)
// use anyhow::{Context, Result};
//
// fn read_config() -> Result<String> {
//     let content = std::fs::read_to_string("config.toml")
//         .context("Failed to read config file")?;
//     Ok(content)
// }

// thiserror Style(Concept Examples,No execution required)
// use thiserror::Error;
//
// #[derive(Error, Debug)]
// enum MyError {
//     #[error("IO error: {0}")]
//     Io(#[from] std::io::Error),
//
//     #[error("Parse error: {0}")]
//     Parse(#[from] std::num::ParseIntError),
// }

anyhow Encourages you to focus on "how to handle errors" rather than "how to define errors"; thiserror Encourages you to use annotations instead of hand-coding Display + From implementations. The two are typically used in conjunction: libraries use thiserror to define fine-grained error types, while applications use anyhow to propagate them uniformly.


▶ Example 5: Comprehensive Exercise—Multi-Layer Error Propagation and Recovery (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
=== User Processing Test ===
Success: <result>
Failure: <e>

=== Batch Processing (Fault-Tolerant) ===
OK: <result>
Skip: <e>
Success: 0/<inputs.len()>
RUST
// ============================================
// Comprehensive Example:Custom Errors + ? Dissemination + Recovery Strategy
// ============================================

use std::fmt;

#[derive(Debug)]
enum AppError {
    ParseError(String),
    ValidationError(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(msg) => write!(f, "Parsing error: {}", msg),
            AppError::ValidationError(msg) => write!(f, "Validation Error: {}", msg),
            AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
        }
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self {
        AppError::ParseError(e.to_string())
    }
}

fn parse_age(input: &str) -> Result<u8, AppError> {
    let age: u8 = input.parse().map_err(|_| AppError::ParseError(format!("'{}' Not a valid number", input)))?;
    if age > 150 {
        return Err(AppError::ValidationError(format!("Age {} Unreasonable", age)));
    }
    Ok(age)
}

fn find_user(id: u32) -> Result<String, AppError> {
    let users = [(1, "Alice"), (2, "Bob"), (3, "Charlie")];
    users.iter()
        .find(|(uid, _)| *uid == id)
        .map(|(_, name)| name.to_string())
        .ok_or_else(|| AppError::NotFound(format!("User ID={}", id)))
}

fn process_user(id_str: &str, age_str: &str) -> Result<String, AppError> {
    let id: u32 = id_str.parse().map_err(|_| AppError::ParseError(format!("Invalid ID: '{}'", id_str)))?;
    let age = parse_age(age_str)?;
    let name = find_user(id)?;
    Ok(format!("User: {}, Age: {}", name, age))
}

fn main() {
    let test_cases = [
        ("1", "30"),
        ("2", "200"),
        ("5", "25"),
        ("abc", "30"),
        ("3", "abc"),
    ];

    println!("=== User Processing Test ===");
    for (id, age) in &test_cases {
        match process_user(id, age) {
            Ok(result) => println!("Success: {}", result),
            Err(e) => println!("Failure: {}", e),
        }
    }

    println!("\n=== Batch Processing (Fault-Tolerant) ===");
    let inputs = [("1", "30"), ("5", "25"), ("2", "abc"), ("3", "20")];
    let mut success_count = 0;
    for (id, age) in &inputs {
        match process_user(id, age) {
            Ok(result) => { println!("OK: {}", result); success_count += 1; }
            Err(e) => println!("Skip: {}", e),
        }
    }
    println!("Success: {}/{}", success_count, inputs.len());
}

Output:

TEXT 📖 Display only
=== User Processing Test ===
Success: User: Alice, Age: 30
Failure: Validation Error: Age 200 Unreasonable
Failure: Not found: User ID=5
Failure: Parsing error: Invalid ID: 'abc'
Failure: Parsing error: 'abc' Not a valid number

=== Batch Processing (Fault-Tolerant) ===
OK: User: Alice, Age: 30
Skip: Not found: User ID=5
Skip: Parsing error: 'abc' Not a valid number
OK: User: Charlie, Age: 20
Success: 2/4

Custom AppError enumeration + From conversion + ? propagation make the error-handling chain clear and concise. map_err converts underlying errors into custom types. For batch processing, use match for fault tolerance—it does not interrupt the loop, but instead logs failures and continues.


❓ FAQ

Q Is unwrap() considered a bad practice?
A Yes, unless you are certain no errors will occur.
Q ? operator vs. match error handling—which should take precedence?
A Prioritize ? for error propagation, and use match only where fine-grained handling is required.
Q My function contains multiple error types (IO errors, parsing errors, business errors). How can ? propagate them uniformly?
A Through automatic conversion by the From trait.
Q Can panic! be caught?
A Yes, but it should not be used as a standard error-handling method.
Q Why do custom error types need to implement both Display and Debug?
A Because Rust’s Error trait requires both.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Write a function fn parse_age(input: &str) -> Result<u8, String> that parses a string into an age (0–150). If parsing fails or the value is out of range, return the corresponding error message. In main, use match to handle three scenarios: a valid age, a non-numeric input, and a value out of range.

  2. Difficulty ⭐⭐: Write a scenario with nested function calls—function A calls function B, function B calls function C, and each level may fail. Use the ? operator to propagate errors. Scenario: read_user_file() -> parse_user_data() -> validate_age(). Each level returns the same custom error type UserDataError (defined using an enumeration), which includes three variants: FileNotFound, ParseFailed(String), and InvalidAge(i32).

  3. Difficulty ⭐⭐⭐: Design a mini calculator that supports four operations: add, subtract, multiply, and divide. All operations propagate errors through ?. Requirements:

    • Define CalcError enum (DivideByZero, Overflow, InvalidOperator(String))
    • Implement Display + Debug
    • Use ? to chain multiple operations together: string expressions such as calculate("10 + 5 * 2")
    • Tip: First split the string by spaces, then process each part individually using fold or a loop.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏