Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to pretty print Decimal value at debug time? #510

Open
ebadta81 opened this issue Apr 24, 2022 · 10 comments
Open

how to pretty print Decimal value at debug time? #510

ebadta81 opened this issue Apr 24, 2022 · 10 comments

Comments

@ebadta81
Copy link

ebadta81 commented Apr 24, 2022

Hello,

I have read #126 but it doesn't help me.

I use Decimal in a VecDeque field.

#[derive(Clone, Debug)]
pub struct mystruct {
   pub myfield: VecDeque<Decimal>
}

How can I see the actual numbers at debug?

I use lldb and vscode.

@paupino
Copy link
Owner

paupino commented May 12, 2022

This is an interesting one as it seems that lldb prints the struct layout by default. In essence, it's related to #204

To simplify:

fn test_func() {
    let value = dec!(16);
    let deq = VecDeque::from([dec!(-1), dec!(0), dec!(1), value]);
    let s = mystruct { myfield: deq  };
    println!("{:?}", value);
}

The lldb debugger prints the following:

(lldb) p s
(testing::mystruct) $0 = {
  myfield = size=4 {
    [0] = {
      flags = 2147483648
      hi = 0
      lo = 1
      mid = 0
    }
    [1] = {
      flags = 0
      hi = 0
      lo = 0
      mid = 0
    }
    [2] = {
      flags = 0
      hi = 0
      lo = 1
      mid = 0
    }
    [3] = {
      flags = 0
      hi = 0
      lo = 16
      mid = 0
    }
  }
}
(lldb) p value
(rust_decimal::decimal::Decimal) $1 = {
  flags = 0
  hi = 0
  lo = 16
  mid = 0
}

Unfortunately, this is a limitation of the pretty printers at the moment. I think it's unlikely to patch the providers directly for Rust Decimal, hence I'm hoping a more extensible method will eventually land.

For the meantime, if you're using windows you may be able to leverage natvis? I haven't tried it myself so don't have a sample config, however it may be a temporary solution.

@ebadta81
Copy link
Author

ebadta81 commented Dec 28, 2022

Unfortunately, I use macos.

Do I have an option?

I cannot even call methods at debug time...

@paupino
Copy link
Owner

paupino commented Jan 13, 2023

Unfortunately, there is no easy solution right now.

The ideal solution would be to be able to implement Debug visualizations as suggested here: rust-lang/rfcs#3191 (comment)

In lieu of that, we may be able to write an lldb python script to extract the data. It may require reconstituting the parts, however I'd need to do a bit more research.

@theonekeyg
Copy link

theonekeyg commented Mar 22, 2023

I think debugger extensions could work, even though RFC3191 is still only in nightly rust. Can put something like this to lib.rs, no action from library user will be necessary.

#![debugger_visualizer(gdb_script_file = "../gdb/rust-decimal-gdb.py")]

@ebadta81
Copy link
Author

ebadta81 commented Apr 7, 2023

Thank You @theonekeyg

Where is this "../gdb/rust-decimal-gdb.py" file?

Should I put this line in the rust-decimal project, or in my project?

I need this for lldb. I am on macos.

@theonekeyg
Copy link

theonekeyg commented Apr 16, 2023

@ebadta81 I assume this file doesn't exist yet, this was addressed to @paupino as a feature proposal that would solve this issue. Really looking forward to solve this bug, personally had to remove rust-decimal from most of my projects, since debugging code with Decimal through standard debuggers is unbearable.

@paupino
Copy link
Owner

paupino commented Apr 19, 2023

Since the debugger_visualizer is close to being stabilized I will likely wait for that before modifying the library. That said, if you need a pretty printer you could do something like this. It's not the most efficient, however should work.

Copy the following python code into a file decimal_printer.py:

import lldb
import decimal

class RustDecimalProvider(object):
    "Print a rust_decimal::Decimal"

    def __init__(self, valobj, internalDict):
        self.valobj = valobj
        self.lo = self.valobj.GetChildMemberWithName('lo').GetValueAsUnsigned()
        self.mid = self.valobj.GetChildMemberWithName('mid').GetValueAsUnsigned()
        self.hi = self.valobj.GetChildMemberWithName('hi').GetValueAsUnsigned()
        self.flags = self.valobj.GetChildMemberWithName('flags').GetValueAsUnsigned()
        self.scale = (self.flags & 0x00FF0000) >> 16
        self.sign = self.flags >> 31

    def num_children(self): 
        return 1

    def get_child_index(self, name): 
        return 0

    def get_child_at_index(self, index):
        child_type = self.valobj.target.GetBasicType(lldb.eBasicTypeChar)
        byte_order = self.valobj.GetData().GetByteOrder()
        data = lldb.SBData.CreateDataFromCString(byte_order, child_type.GetByteSize(), self.build_decimal())
        return self.valobj.CreateValueFromData("Decimal", data, child_type.GetArrayType(data.GetByteSize()))
            
    def update(self): 
        return True

    def has_children(self): 
        return True

    def build_decimal(self):
        mantissa = decimal.Decimal(self.hi)
        shift = decimal.Decimal(4294967296)
        mantissa = (mantissa * shift) + decimal.Decimal(self.mid)
        mantissa = (mantissa * shift) + decimal.Decimal(self.lo)
        value = mantissa
        divisor = decimal.Decimal(10)
        for i in range(self.scale):
            value = value / divisor
        if self.sign > 0:
            value = value * -1
        return str(value)
        
def __lldb_init_module(debugger, dict):
    debugger.HandleCommand(
        'type synthetic add -x "Decimal" --python-class decimal_printer.RustDecimalProvider -w Rust')

To configure this you'd need to load this into your LLDB context via:

command script import decimal_printer.py

Of course, to avoid having to do this every time, you can add a line into your ~/.lldbinit to automatically load the context at startup. Using it, you'd achieve output like:

(lldb) command script import decimal_printer.py
(lldb) p value
(dec_debug::Wrapper) $35 = {
  value = {
    Decimal = "1.234"
  }
}

@paupino
Copy link
Owner

paupino commented Apr 19, 2023

For verboseness, the example above was the following code:

use rust_decimal::prelude::*;

struct Wrapper {
    value: Decimal
}

fn main() {
    let value = Decimal::new(1234, 3);
    let value = Wrapper { value };
    do_something(value);
}

fn do_something(value: Wrapper) {
    println!("{}", value.value);
}

I started the program using rust-lldb. e.g.:

cargo build
rust-lldb target/debug/deps/dec_debug-eb9cfdceaca60ae6

Once in LLDB, I set a breakpoint:

(lldb) breakpoint set --name do_something
Breakpoint 1: where = dec_debug-eb9cfdceaca60ae6`dec_debug::do_something::hd10d7801e5b0a11d + 12 at main.rs:14:5, address = 0x0000000100003348

We then started the program:

(lldb) run
Process 45332 launched: '/Users/paulmason/dev/tmp/dec-debug/target/debug/deps/dec_debug-eb9cfdceaca60ae6' (arm64)
Process 45332 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003348 dec_debug-eb9cfdceaca60ae6`dec_debug::do_something::hd10d7801e5b0a11d(value=Wrapper @ 0x000000016fdfe920) at main.rs:14:5
   11  	}
   12
   13  	fn do_something(value: Wrapper) {
-> 14  	    println!("{}", value.value);
   15  	}
Target 0: (dec_debug-eb9cfdceaca60ae6) stopped.

Once the breakpoint was hit we can see the two different outcomes. Before we load the pretty printer:

(lldb) p value
(dec_debug::Wrapper) $0 = {
  value = {
    flags = 196608
    hi = 0
    lo = 1234
    mid = 0
  }
}

After we load the pretty printer:

(lldb) command script import decimal_printer.py
(lldb) p value
(dec_debug::Wrapper) $1 = {
  value = {
    Decimal = "1.234"
  }
}

@Tony-Samuels
Copy link
Collaborator

This is stabilised and the docs on how to do so are available here.

I suspect the MR to do this should be pretty easy for GDB given the text above. Although adding testing for it might be harder?

@matthiasg
Copy link

Unfortunately, there is no easy solution right now.

The ideal solution would be to be able to implement Debug visualizations as suggested here: rust-lang/rfcs#3191 (comment)

In lieu of that, we may be able to write an lldb python script to extract the data. It may require reconstituting the parts, however I'd need to do a bit more research.

There might be a rather easy solution. Add a feature flag which extends the structure to contain an approximate f64 representation of the decimal, update it every time the decimal state changes. That way we can inspect it during debug and it has no relevancy or overhead without the feature flag. It might also be bound to debug compile, but i think it should at least be guarded via a feature flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants