Rust Programming :The Complete Developor‘s Guide--04 Ownership & Lifetimes

Ownership & Lifetimes

Ownership Review

  • Data in Rust programs have an owner
    • Owner os responsible for cleaning up data
      • Memory management
    • Only one owner (by default)
    • Functions, closures, structs, enums, and scopes are owners
  • Data can be transferred (moved) from one owner to another
    • Function calls, variable reassignment and closures
  • Possible to “borrow” data from an onwer
    • Owner still responsible for clean up

Ownership Review - Example

#[derive(Debug)]
enum FrozenItem {
    IceCube,
}

#[derive(Debug)]
struct Freezer {
    contents: Vec<FrozenItem>,
}

fn place_item(freezer: &mut Freezer, item: FrozenItem) {
    freezer.contents.push(item);
}

fn main() {
    let mut freezer = Freezer { contents: vec![] };
    let cube = FrozenItem::IceCube;
    place_item(&mut freezer, cube);
    // cube no longer available
}

Lifetimes

  • A way to inform the compiler that borrowed data will be valid at a specific point in time
  • Needed for
    • Storing borrowed data in structs or enums
    • Returning borrowed data from functions
  • All data has a lifetime
    • Most cases are elided

Lifetime Syntax - struct

  • Convention use 'a, 'b, 'c
  • 'static is reserved
    • 'static data stays in memory until the program terminates
struct Name<'a> {
    field: &'a DataType,
}

Lifetime Example - struct

enum Part {
    Bolt,
    Panel,
}

struct RobotArm<'a> {
    part:&'a Part,
}

struct AssemblyLine {
    parts:Vec<Part>,
}

fn main() {
    let line = AssemblyLine { parts: vec![Part::Bolt, Part::Panel]};
    {
        let arm = RobotArm { part: &line.parts[0]};
    };
    // arm no longer exists
}

Lifetime Syntax - function

fn name<'a>(arg:&'a DataType) -> &'a DataType {}

Solidifying understanding

  • Lifetime annotations indicate that there exists some owned data that:
    • Lives at least as long as the borrowed data
    • Outlives or outlasts the scope of a borrow
    • Exists longer than the scope of a borrow
  • Structures utilizing borrowed data must:
    • Always be created after the owner was created
    • Always be destroyed before the owner is destroyed
      Recap
  • Lifetimes allow:
    • Borrowed data in a structure
    • Returning references from functions
  • Lifetimes are the mechanisim that tracks how long a piece of data resides in memory
  • Lifetimes are usually elided, but can be specified manually
  • Lifetimes will be checked by the compiler

Demo Lifetimes

#[derive(Debug)]
struct Cards {
    inner: Vec<IdCard>,
}

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
enum City {
    Barland,
    Bazopolis,
    Fooville,
}

#[derive(Debug)]
struct IdCard {
    name: String,
    age: u8,
    city: City,
}

impl IdCard {
    pub fn new(name: &str, age: u8, city: City) -> Self {
        Self {
            name: name.to_string(),
            age,
            city,
        }
    }
}

fn new_ids() -> Cards {
    Cards {
        inner: vec![
            IdCard::new("Amy", 1, City::Fooville),
            IdCard::new("Matt", 10, City::Barland),
            IdCard::new("Bailee", 20, City::Barland),
            IdCard::new("Anthony", 30, City::Bazopolis),
            IdCard::new("Tina", 40, City::Bazopolis),
        ],
    }
}

#[derive(Debug)]
struct YoungPeople<'a> {
    inner: Vec<&'a IdCard>,
}

impl<'a> YoungPeople<'a> {
    fn living_in_fooville(&self) -> Self {
        Self {
            inner: self
                .inner
                .iter()
                .filter(|id| id.city == City::Fooville)
                .map(|id| *id)
                .collect(),
        }
    }
}

fn main() {
    let ids = new_ids();
    let young = YoungPeople {
        inner: ids.inner.iter().filter(|id| id.age <= 20).collect(),
    };

    println!("ids");
    for id in ids.inner.iter() {
        println!("{:?}", id);
    }

    println!("\nyoung");
    for id in young.inner.iter() {
        println!("{:?}", id);
    }
}

Activity Lifetimes & Structures

// Topic: Lifetimes & Structures
//
// Requirements
// * Display just the names and titles of persons from the mock-data.csv file
// * The names & titles must be stored in a struct separately from the mock
//   data for potential later usage
// * None of the mock data may be duplicated in memory
//
// Notes:
// * The mock data has already been loaded with the include_str! macro, so all funtionality
//   must be implemented using references/borrows
// id, first_name, email, dept, title
const MOCK_DATA: &'static str = include_str!("mock-data.csv");

struct Names<'a> {
    inner: Vec<&'a str>,
}

struct Titles<'a> {
    inner: Vec<&'a str>,
}

fn main() {
    let data = MOCK_DATA.split('\n').skip(1).collect();
    let names = data
        .iter()
        .filter_map(|line| line.split(',').nth(1))
        .collect();

    let names = Names { inner: names };

    let titles = data
        .iter()
        .filter_map(|line| line.split(',').nth(4))
        .collect();
    let titles = Titles { inner: titles };

    let data = names.inner.iter().zip(titles.inner.iter());

    for (name, title) in data.take(5) {
        println!("name: {}, title: {}", name, title);
    }
}

Activity Lifetimes & Function

fn longest<'a>(one:&'a str, two:&'a str) ->&'a str {
    if two > one {
        return two;
    }else {
        return one;
    }
}

fn main(){
    let short = "hello";
    let long = "this is a long message";
    println!("{}",longest(short, long));
}

Custom Errors

  • Functions may fail in more than one way
  • Useful to communicate the failure reason
  • Error enumeration
    • Enumertations allow errors to be easily defined
    • Can match on the enumeration to handle specific error conditions

Error Requirements

  • Implement the Debug trait
    • Display error info in debug contexts
  • Implement the Dispaly trait
    • Display error info in user contexts
  • Implement the Error trait
    • Interop with code using dynamic errors
      Manual Error Creation
#[derive(Debug)]
enum LockError{
    MechanicalError(i32),
    NetworkError,
    NotAuthorized,
}

use std::error::Error;
impl Error for LockError {}

use std::fmt;
impl fmt::Display for LockError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
        match self{
            self::MechanicalError(code) => write!(f, "Mechanical Error: {}", code),
            self::NetworkError => write!(f, "Network Error"),
            self::NotAuthorized => write!(f, "Not Authorized"),
        }
    }
}

The ‘thiserror’ Crate

# Cargo.toml
[dependencies]
thiserror = "1.0"
use thiserror::Error;
#[derive(Debug, Error)]
enum LockError{
    #[error("Mechanical Error:{0}")]
    MechanicalError(i32),
    #[error("Network Error")]
    NetworkError,
    #[error("Not Authorized")]
    NotAuthorized,
}

Usage

fn lock_door() -> Result<(), LockError> {
    // ... some code ...
    Err(LockError::NetworkError)
}

Error Conversion

use thiserror::Error;
#[derive(Debug, Error)]
enum NetworkError {
    #[error("Connection time out")]
    Timeout,
    #[error("Unreachable")]
    Unreachable
}

enum LockError{
    #[error("Mechanical Error:{0}")]
    MechanicalError(i32, i32),
    #[error("Network Error")]
    Network(#[from] NetworkError),
    #[error("Not Authorized")]
    NotAuthorized,
}

Pro Tips: Do’s

  • Prefer to use error enumerations over strings
    • More concisely commmunicates the problem
    • Can be used with match
    • Strings are Ok when prototyping, or if the problem domain isn’s fully understood
      • Change to enumerations as soon as possible
  • Keep errors specific
    • Limit error enumerations to:
      • Single modules
      • Single functions
  • Try to use match as much as possible
    More Pro Tips: Don’ts
  • Don’t put unrelated errors into a single enumeration
    • As the problem domain expands, the enumeration will become unwiedy
    • Changes to the enumeration will cascade across the entire codebase
    • Unclear witch errors can be generated by a function
      Recap
  • Custom error enumerations communicate exactly what wrong in a function
  • Errors require three trait implementations
    • Debug (can be derived)
    • std::error::Error (empty impl ok)
    • Display (manual or crate)
  • Use the thiserror crate to easily implement all required traits for errors
  • Keep error enumerations module or function specific
    • Don’t put too many variants in one error

Demo Custom Errors

use chrono::{DateTime, Duration, Utc};
use thiserror::Error;

struct SubwayPass {
    id: usize,
    funds: isize,
    expires: DateTime<Utc>,
}

#[derive(Error, Debug)]
enum PassError {
    #[error("data store disconnected")]
    PassExpired,
    #[error("insufficient funds: {0}")]
    InfufficientFunds(isize),
    #[error("pass read error: {0}")]
    ReadError(String),
}

fn swipe_card() -> Result<SubwayPass, PassError> {
    Ok(SubwayPass {
        id: 0,
        funds: 200,
        expires: Utc::now() + Duration::weeks(52),
    })
    // Err(PassError::ReadError("Magstrip failed to read message".to_owned()))
}

fn use_pass(pass: &mut SubwayPass, cost: isize) -> Result<(), PassError> {
    if Utc::now() > pass.expires {
        Err(PassError::PassExpired)
    } else {
        if pass.funds - cost < 0 {
            Err(PassError::InfufficientFunds(pass.funds))
        } else {
            pass.funds = pass.funds - cost;
            Ok(())
        }
    }
}

fn main() {
    let pass_status = swipe_card().and_then(|mut pass| use_pass(&mut pass, 3));
    match pass_status {
        Ok(_) => println!("ok to board"),
        Err(e) => match e {
            PassError::ReadError(s) => (),
            PassError::PassExpired => (),
            PassError::InfufficientFunds(f) => (),
        },
    }
}

Demo const

const MAX_SPEED: i32 = 9000;

fn clamp_speed(speed: i32) -> i32 {
    if speed > MAX_SPEED {
        9000
    } else {
        speed
    }
}

fn main() {}

Demo New Types

#[derive(Debug, Copy, Clone)]
struct NeverZero(i32);

impl NeverZero {
    fn new(x: i32) -> Result<Self, String> {
        if i == 0 {
            Err("cannot be zero".to_owned())
        } else {
            Ok(Self(i))
        }
    }
}

fn divide(a: i32, b: NeverZero) -> i32 {
    let b = b.0;
    a / b
}

fn main() {
    match NeverZero::new(5) {
        Ok(nz)=> println!("{:?}", divide(10, nz)),
        Err(e) => println!("{:?}",e),
    }
}

Activity New Types

#[derive(Debug)]
enum Color {
    Black,
    Blue,
    Brown,
    Custom(String),
    Gray,
    Green,
    Purple,
    Red,
    White,
    Yellow,
}

/// Each new type should implement a 'new' function
#[derive(Debug)]
struct ShirtColor(Color);
impl ShirtColor {
    fn new(color: Color) -> Self {
        Self(color)
    }
}

#[derive(Debug)]
struct ShoesColor(Color);
impl ShoesColor {
    fn new(color: Color) -> Self {
        Self(color)
    }
}

#[derive(Debug)]
struct PantsColor(Color);
impl PantsColor {
    fn new(color: Color) -> Self {
        Self(color)
    }
}

fn print_shirt_color(color: ShirtColor) {
    println!("shirt color = {:?}", color);
}

fn print_shoes_color(color: ShoesColor) {
    println!("shoes color = {:?}", color);
}

fn print_pants_color(color: PantsColor) {
    println!("pants color = {:?}", color);
}

fn main() {
    let shirt_color = ShirtColor::new(Color::Gray);
    let shoes_color = ShoesColor::new(Color::Blue);
    let pants_color = PantsColor::new(Color::White);

    print_shirt_color(shirt_color);
    print_shoes_color(shoes_color);
    print_pants_color(pants_color);
}

Typesstate Pattern

**Typestates

  • Leverage type system to encode state changes
  • Implemented by creating a type for each state
    • Use move semantics to invalidate a state
    • Return next state from previous state
    • Optionally drop the state
      • Close file, connection dropped, etc
  • Complie time enforcement of logic
struct BusTicket;
struct BoardedBusTicket;

impl BusTicket {
    fn board(self) -> BoardedBusTicket {
        BoardedBusTicket
    }
}

fn main() {
    let ticket = BusTicket;
    let board = ticket.board();
    // Complie error
    // use of moved value: `ticket`
    ticket.board();
}
struct File<'a>(&'a str);
impl<'a> File<'a> {
    fn read_bytes(&self) -> Vec<u8> {
        // read data
        let v: Vec<u8> = vec![];
        v
    }

    fn delete(self) {
        // delete file
    }
}

fn main() {
    let file = File("data.txt");
    let data = file.read_bytes();
    file.delete();

    //Compile error
    let read_again = file.read_bytes();
}

Recap

  • Typestates leverage the compiler to enforce logic
  • Can be used for:
    • Invalidating / consuming states
    • Properly transsitioning to another state
    • Disallowing access to a missing resource

Demo Typestate Pattern

struct Employee<State> {
    name: String,
    state: State,
}

impl<State> Employee<State> {
    fn transition<NextState>(self, state: NextState) -> Employee<NextState> {
        Employee {
            name: self.name,
            state: state,
        }
    }
}

struct Agreement;
struct Signature;
struct Training;
struct FailedTraining {
    score: u8,
}

struct OnBoardingComplete {
    score: u8,
}

impl Employee<Agreement> {
    fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            state: Agreement,
        }
    }

    fn read_agreement(self) -> Employee<Signature> {
        self.transition(Signature)
    }
}

impl Employee<Signature> {
    fn sign(self) -> Employee<Training> {
        self.transition(Training)
    }
}

#[rustfmt::skip]
impl Employee<Training> {
    fn train(self, score: u8) -> Result<Employee<OnBoardingComplete>, Employee<FailedTraining>> {
        if score >= 7 {
            Ok(self.transition(OnBoardingComplete { score }))
        } else {
            Err(self.transition(FailedTraining { score }))
        }
    }
}

fn main() {
    let employee = Employee::new("Sara");
    let onboarded = employee.read_agreement().sign().train(6);
    match onboarded {
        Ok(completed) => println!("Completed"),
        Err(emp) => println!("training failed, score:{}", emp.state.score),
    }
}

Activity Typestate Pattern

#[derive(Copy, Debug, Clone)]
struct LuggageId(usize);
struct Luggage(LuggageId);

struct CheckIn(LuggageId);
struct OnLoad(LuggageId);
struct OffLoad(LuggageId);
struct AwaitingPickup(LuggageId);
struct EndCustody(LuggageId);

impl Luggage {
    fn new(id: LuggageId) -> Self {
        Luggage(id)
    }

    fn check_in(self) -> CheckIn {
        CheckIn(self.0)
    }
}

impl CheckIn {
    fn onload(self) -> OnLoad {
        OnLoad(self.0)
    }
}

impl OnLoad {
    fn offload(self) -> OffLoad {
        OffLoad(self.0)
    }
}

impl OffLoad {
    fn carousel(self) -> AwaitingPickup {
        AwaitingPickup(self.0)
    }
}

impl AwaitingPickup {
    fn pickup(self) -> (Luggage, EndCustody) {
        (Luggage(self.0), EndCustody(self.0))
    }
}

fn main() {
    let id = LuggageId(1);
    let luggage = Luggage::new(id);
    let luggage = luggage.check_in().onload().offload().carousel();

    let (luggage, _) = luggage.pickup();
    println!("luggage {:?}", luggage.0);
}
enum Species {
    Finch,
    Hawk,
    Parrot,
}

struct Bird {
    age:usize,
    species:Species,
}

#[rustfmt::ship]
fn main() {
    let hawk = Bird{
        age: 13, 
        species:Species::Hawk,
    };

    match hawk {
        Bird{age:4, ..} => println!("4 years old bird"),
        Bird{age:4..=10| 15..=20, ..} => println!("4~10 or 15~20 years old bird"),
        Bird{species:Species::Finch, ..} => println!("finch!"),
        Bird{..}=> println!("other bird"),
    }
}

Demo Match Guards & Binding

enum

enum Status {
    Error(i32),
    Info,
    Warn,
}

fn main() {
    let status = Status::Error(8);
    match status {
        /// @ symbols this is called bingding
        /// bingding some variable to the value
        /// bind 3 to s
        Status::Error(s @ 3) => println!("error three"),
        /// 
        Status::Error(s @ 5..=6) => println!("error 5 or 6:{}", s),
        Status::Error(s @ 4..=10) => println!("error three througt ten:{}", s),
        Status::Error(s @ 18 | s @ 19) => println!("error 18 or 19"),
        Status::Error(s) => println!("error code:{}", s),
        Status::Info => println!("info"),
        Status::Warn => println!("warn"),
    }
}

struct

struct Vehicle {
    km: usize,
    year: usize,
}

#[rustfmt::ship]
fn main() {
    let car = Vehicle {
        km: 80_000,
        year: 2020,
    };

    match car {
        Vehicle { km, year } if km == 0 && year == 2020 => println!("new 2020 car"),
        Vehicle { km, .. } if km <= 50_000 => println!("under 50k km"),
        Vehicle { km, .. } if km >= 80_000 => println!("at least 80k km"),
        Vehicle { year, .. } if year == 2020 => println!("made in 2020"),
        Vehicle { .. } => println!("other mileage"),
    }
}

Activity Match Guards & Binding

#[derive(Debug)]
enum TreasureItem {
    Gold,
    SuperPower,
}

#[derive(Debug)]
struct TreasureChest {
    content: TreasureItem,
    amount: usize,
}

#[derive(Debug)]
struct Pressure(u16);

#[derive(Debug)]
enum BrickStyle {
    Dungenon,
    Gray,
    Red,
}

#[derive(Debug)]
enum Tile {
    Brick(BrickStyle),
    Dirt,
    Grass,
    Sand,
    Treasure(TreasureChest),
    Water(Pressure),
    Wood,
}

fn print_tile(tile: Tile) {
    use Tile::*;
    match tile {
        Brick(brick @ BrickStyle::Red | brick @ BrickStyle::Gray) => {
            println!("the brick color is {:?}", brick)
        }
        Brick(other) => println!("{:?} brick", other),
        Dirt | Grass | Sand => println!("Ground tile"),
        Treasure(TreasureChest { amount, .. }) if amount >= 100 => println!("lots of gold"),
        Water(pressure) if pressure.0 < 10 => println!("water pressure leavel:{:?}", pressure.0),
        Water(pressure) if pressure.0 >= 10 => println!("High water pressure"),
        _ => (),
    }
}

fn main() {
    let tile = Tile::Brick(BrickStyle::Red);
    print_tile(tile);

    let tile = Tile::Sand;
    print_tile(tile);

    let tile = Tile::Treasure(TreasureChest {
        content: TreasureItem::Gold,
        amount: 200,
    });
    print_tile(tile);

    let tile = Tile::Water(Pressure(9));
    print_tile(tile);
}

Slices Arrays & Slices

Arrays

  • Contiguous memory region
  • All elements have the same size
  • Arrays are not dynamically sized
    • Size must be hard-coded
    • Usually prefer Vector
  • Useful when writing algorithms with a fixed buffer size
    • Networkong, cropto, matrices
      Syntax
let numbers = [1,2,3,4];
/// [type; element count]
let numbers: [u8; 3] = [1,2,3];

Slices

  • A borrowed view into an array
  • Can be iterated upon
  • Optionally mutable
  • Indices bounded by the slice size
    • Cannot go out of bounds of the initial slice
  • Can create any number of subslices from an existing slice

Slices - View Into An Array

 [char; 10]
 Array    0  1  2  3  4  5  6  7  8  9
          A  B  C  D  E  F  G  H  I  J
 Slice          0  1  2  3
 &[char]

Slices & Vectors

  • Borrowing a Vector as an argument to a function that requires a slice will automatically obtain a slice
  • Always prefer to borrow a slice instead of a Vector
fn func(slice: &[u8]) {}
let numbers = vec![1, 2, 3];
func(&numbers);
let numbers: &[u8] = numbers.as_slice();

Slicing With Ranges

let chars = vec!['A', 'B', 'C', 'D'];
let bc = &chars[1..=2];
let ab = &chars[0..2];

  Vector            Vector
0  1  2  3        0  1  2  3
A  B  C  D        A  B  C  D
   0  1           0  1
  Slice              Slice

Subslices

let chars = vec!['A', 'B', 'C', 'D'];
let bcd = &chars[1..=3];
let cd  = &bcd[1..=2];

  Vector            Slice
0  1  2  3         0  1  2
A  B  C  D   -->   B  C  D
   0  1  2            0  1
  Slice             Slice

Recap

  • Array must be statically initialized with hard-coded lengths
  • Slices are a way to access parts of an array
  • Array-backed data structures like Vectors can be sliced
  • Slice lengths are always bound by the size of the slice
  • Subslices can be created from existing slices

Slice Patterns

Use Case

  • Read the first few bytes to determine header information
    • Take different actions based on the data using match
  • Get the first or last elements of a slice
  • No need for bounds checking on slices
    • Compiler ensures access are always within bounds
      Example
let chars = vec!['A', 'B', 'C', 'D'];
match chars.as_slice() {
    // first take the first element of the slice
    // last take the last element of the slice
    // .. ignore everything between the first and the last
    [first, .., last] => (),
    // match one element
    [single] => (),
    // match an empty slice
    [] => (),
}

let chars = vec!['A', 'B', 'C', 'D'];
match chars.as_slice() {
    [one, two, ..] => (),
    [.., last] => (),
    [] => (),
}

Overlapping Patterns

  • Patterns easily overlap
  • Minimize number of match arms to avoid bugs
// second arm always ignored
match slice {
    [first, ..] => (),
    [.., last] => (),
    [] => (),
}

Prevent Overlapping Patterns

  • Match the largest patterns first, followed by smaller patters
match slice {                       slice {
    [] => (),                              [a, b, c, d, .., ] => (),
    [a, ..] => (),                         [a, b, c, .., ] => (),
    [a, b, .., ] => (),                    [a, b, .., ] => (),
    [a, b, c, .., ] => (),                 [a, ..] => (),   
    [a, b, c, d, .., ] => (),              [] => (),   
}                                     }
first two arms cover all cases,       all arms can be matched
remaining will be ignored

Guars

let nums = vec![7, 8, 9];
match nums.as_slice() {
    [first @ 1..=3, rest @ ..] =>{

    }
    [single] if single == &5 || single ==&6 => ()
    [a, b] => (),
    [..]=>(),
    []=>(),
}

Recap

  • Slices can be matched on specific patterns
    • These patterns can include match guards
  • Match on largest patterns first, followed by smaller patterns
    • Smaller patterns tent to be greedy
  • Minimize the number of match arms to avoid bugs

Activity Slices

fn data() -> &'static [u64] {
    &[5, 5, 4, 4, 3, 3, 1]
}

fn process_chunk(data: &[u64]) {
    match data {
        [lhs, rhs] => println!("{} + {} = {}", lhs, rhs, (lhs + rhs)),
        [single] => println!("Unpaired value:{}", single),
        [] => println!("Data stream complete"),
        [..] => unreachable!("chunk size should be at most 2"),
    }
}
fn main() {
    // 'stream' is an iterator of Option<&[u64]>
    let mut stream = data().chunks(2);

    for chunk in stream {
        process_chunk(chunk);
    }
}

Type Aliases

  • Give a new name to an existing type
    • Basic text substitution
  • Simplifies complicated types
  • Makes code easier to read & write
  • Multiple aliases for the same type will work together, but masybe not as intended
    Syntax
type Name = Type;

Example

type ContactName = String;
type Miles = u64;
type Centimeters = u64;

type Callback = HashMap<String, Box<Fn(i32, i32) -> i32>>;

Usage

struct Contact {
    name: String,
    phone: String,
}

type ContactName = String;
type ContactIndex = HashMap<ContactName, Contact>;

fn add_contact(index: & mut ContactIndex, contact: Contact) {
    index.insert(contact.phone.to_owned(), contact);
}

Generics/Lifetimes

type BorrowedItems<'a> = Vec<&'a str>;
type GenericThings<T>  = Vec<Thing<T>>;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

血_影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值